From 0460b9d5876de9a93e2d35e407d8a03aa791e6a7 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Fri, 4 Sep 2026 10:46:13 -0700 Subject: [PATCH] Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9 --- leenkx/Shaders/add_pass/add_pass.json | 17 + .../compositor_pass/compositor_pass.frag.glsl | 108 +- .../compositor_pass/compositor_pass.json | 5 + .../debug_draw/line_deferred.frag.glsl | 2 +- .../Shaders/render_draw/render_line.frag.glsl | 8 + .../Shaders/render_draw/render_line.vert.glsl | 12 + .../render_line_deferred.frag.glsl | 15 + leenkx/Shaders/std/dof.glsl | 9 +- leenkx/Shaders/std/light.glsl | 394 +-- leenkx/Shaders/std/mapping.glsl | 104 +- leenkx/Shaders/std/math.glsl | 8 + leenkx/Shaders/std/morph_target.glsl | 30 + leenkx/Sources/iron/App.hx | 20 +- leenkx/Sources/iron/RenderPath.hx | 11 +- leenkx/Sources/iron/Scene.hx | 71 +- leenkx/Sources/iron/Trait.hx | 4 +- leenkx/Sources/iron/data/Data.hx | 9 +- leenkx/Sources/iron/data/Geometry.hx | 30 + leenkx/Sources/iron/data/SceneFormat.hx | 65 +- leenkx/Sources/iron/data/ShaderData.hx | 9 + leenkx/Sources/iron/format/gif/Data.hx | 357 +++ leenkx/Sources/iron/format/gif/GifEncoder.hx | 359 +++ leenkx/Sources/iron/format/gif/LzwEncoder.hx | 350 +++ leenkx/Sources/iron/format/gif/NeuQuant.hx | 541 ++++ leenkx/Sources/iron/format/gif/Reader.hx | 346 +++ leenkx/Sources/iron/format/gif/Tools.hx | 390 +++ leenkx/Sources/iron/format/gif/Writer.hx | 525 ++++ leenkx/Sources/iron/format/jpg/Data.hx | 37 + leenkx/Sources/iron/format/jpg/Writer.hx | 657 +++++ .../Sources/iron/format/jpg/WriterOriginal.hx | 651 +++++ leenkx/Sources/iron/format/wav/Data.hx | 56 + leenkx/Sources/iron/format/wav/Reader.hx | 155 ++ leenkx/Sources/iron/format/wav/Writer.hx | 72 + leenkx/Sources/iron/object/Constraint.hx | 110 +- leenkx/Sources/iron/object/CurveObject.hx | 1338 +++++++++ leenkx/Sources/iron/object/LightObject.hx | 31 +- leenkx/Sources/iron/object/MeshObject.hx | 57 +- leenkx/Sources/iron/object/MorphTarget.hx | 19 + leenkx/Sources/iron/object/Object.hx | 20 +- .../Sources/iron/object/ParticleSystemCPU.hx | 150 +- .../Sources/iron/object/ParticleSystemGPU.hx | 8 +- leenkx/Sources/iron/object/SpeakerObject.hx | 43 +- leenkx/Sources/iron/object/Tilesheet.hx | 11 +- leenkx/Sources/iron/object/Transform.hx | 15 +- leenkx/Sources/iron/object/Uniforms.hx | 26 +- .../leenkx/logicnode/ActiveSceneObjectNode.hx | 12 + .../logicnode/AddParticleToObjectNode.hx | 58 +- .../logicnode/AddPhysicsConstraintNode.hx | 4 +- .../leenkx/logicnode/AddPhysicsHookNode.hx | 46 + .../leenkx/logicnode/AddRigidBodyNode.hx | 44 +- .../leenkx/logicnode/AddSoftBodyNode.hx | 38 + .../Sources/leenkx/logicnode/AddTraitNode.hx | 29 +- .../leenkx/logicnode/AlongCurveNode.hx | 23 + .../Sources/leenkx/logicnode/AnimationNode.hx | 35 + .../logicnode/AroundNavigableLocationNode.hx | 29 + .../Sources/leenkx/logicnode/ArrayAddNode.hx | 2 +- .../leenkx/logicnode/ArrayGetNextNode.hx | 6 +- .../logicnode/ArrayGetPreviousNextNode.hx | 2 +- .../leenkx/logicnode/ArrayInsertNode.hx | 2 +- .../Sources/leenkx/logicnode/ArrayLoopNode.hx | 2 +- .../leenkx/logicnode/ArrayRemoveNode.hx | 2 +- .../leenkx/logicnode/ArrayRemoveValueNode.hx | 2 +- .../leenkx/logicnode/ArrayResizeNode.hx | 2 +- .../Sources/leenkx/logicnode/ArraySetNode.hx | 2 +- .../leenkx/logicnode/ArraySpliceNode.hx | 2 +- .../leenkx/logicnode/AsyncArrayLoopNode.hx | 62 + .../Sources/leenkx/logicnode/AsyncLoopNode.hx | 63 + .../leenkx/logicnode/CallFunctionNode.hx | 10 +- .../Sources/leenkx/logicnode/CameraGetNode.hx | 18 +- .../Sources/leenkx/logicnode/CameraSetNode.hx | 5 + .../leenkx/logicnode/CanvasSetSliderNode.hx | 13 +- .../Sources/leenkx/logicnode/ColorMixNode.hx | 143 +- .../Sources/leenkx/logicnode/ColorsMixNode.hx | 26 + .../leenkx/logicnode/ConvexBreakNode.hx | 73 + .../Sources/leenkx/logicnode/CreateMapNode.hx | 1 - .../leenkx/logicnode/CrowdGoToLocationNode.hx | 24 +- .../leenkx/logicnode/CrowdSetLocationNode.hx | 33 + .../leenkx/logicnode/CurveGuideNode.hx | 33 + .../leenkx/logicnode/DeformCurveNode.hx | 27 + .../leenkx/logicnode/DrawCameraNode.hx | 99 +- .../Sources/leenkx/logicnode/DrawGifNode.hx | 129 + .../leenkx/logicnode/DrawImageRenderNode.hx | 72 +- .../leenkx/logicnode/DrawRoundedRectNode.hx | 5 +- .../leenkx/logicnode/DrawStringNode.hx | 26 +- .../leenkx/logicnode/FollowCurveNode.hx | 47 + .../leenkx/logicnode/FormatNumberNode.hx | 57 + .../leenkx/logicnode/FormatTimeNode.hx | 32 + .../leenkx/logicnode/GetAgentDataNode.hx | 30 +- .../leenkx/logicnode/GetAreaLightDataNode.hx | 27 + .../Sources/leenkx/logicnode/GetAssetsNode.hx | 12 + .../Sources/leenkx/logicnode/GetChildNode.hx | 41 +- .../leenkx/logicnode/GetChildrenNode.hx | 2 +- .../leenkx/logicnode/GetContactsNode.hx | 4 +- .../leenkx/logicnode/GetCrowdDataNode.hx | 39 + .../leenkx/logicnode/GetCurveDataNode.hx | 33 + .../leenkx/logicnode/GetCurveSplineNode.hx | 36 + .../leenkx/logicnode/GetFirstContactNode.hx | 5 +- .../Sources/leenkx/logicnode/GetGroupNode.hx | 2 +- .../leenkx/logicnode/GetImageColorNode.hx | 14 +- .../leenkx/logicnode/GetKeyboardNode.hx | 52 + .../leenkx/logicnode/GetLightDataNode.hx | 37 + .../logicnode/GetMaterialImageParamNode.hx | 31 + .../logicnode/GetMaterialRgbParamNode.hx | 31 + .../logicnode/GetMaterialValueParamNode.hx | 31 + .../leenkx/logicnode/GetObjectByUidNode.hx | 3 - .../logicnode/GetObjectCameraDataNode.hx | 27 + .../leenkx/logicnode/GetObjectGeomNode.hx | 44 +- .../logicnode/GetObjectInstancedNode.hx | 43 + .../Sources/leenkx/logicnode/GetObjectNode.hx | 6 - .../leenkx/logicnode/GetObjectShapeKeyNode.hx | 28 + .../leenkx/logicnode/GetObjectTraitsNode.hx | 4 +- .../leenkx/logicnode/GetParticleDataNode.hx | 2 + .../logicnode/GetPositionSpeakerNode.hx | 33 - .../leenkx/logicnode/GetRigidBodyDataNode.hx | 48 +- .../leenkx/logicnode/GetSoftBodyDataNode.hx | 47 + .../leenkx/logicnode/GetSpeakerDataNode.hx | 48 + .../leenkx/logicnode/GetSpotLightDataNode.hx | 35 + .../leenkx/logicnode/GetTilesheetFlipNode.hx | 6 +- .../leenkx/logicnode/GetTilesheetStateNode.hx | 4 +- .../Sources/leenkx/logicnode/GetTraitNode.hx | 1 + .../leenkx/logicnode/GetTransformNode.hx | 3 +- .../leenkx/logicnode/GetWorldDataNode.hx | 34 + .../leenkx/logicnode/GetWorldStrengthNode.hx | 3 +- .../leenkx/logicnode/GoToLocationNode.hx | 86 +- leenkx/Sources/leenkx/logicnode/GroupNode.hx | 2 +- .../leenkx/logicnode/HasContactArrayNode.hx | 18 +- leenkx/Sources/leenkx/logicnode/LogicTree.hx | 8 + leenkx/Sources/leenkx/logicnode/LookAtNode.hx | 52 +- .../Sources/leenkx/logicnode/MapRangeNode.hx | 19 +- .../Sources/leenkx/logicnode/MaterialNode.hx | 40 +- .../leenkx/logicnode/MathConstantsNode.hx | 16 + leenkx/Sources/leenkx/logicnode/MathNode.hx | 13 +- .../leenkx/logicnode/MatrixMathNode.hx | 28 - .../leenkx/logicnode/MergedKeyboardNode.hx | 12 +- .../leenkx/logicnode/MergedMouseNode.hx | 12 +- .../leenkx/logicnode/MergedSurfaceNode.hx | 12 +- .../logicnode/MergedVirtualButtonNode.hx | 12 +- .../leenkx/logicnode/NavigableLocationNode.hx | 37 +- .../leenkx/logicnode/OnContactArrayNode.hx | 31 +- .../leenkx/logicnode/OnRender2DNode.hx | 12 +- .../leenkx/logicnode/PauseActionNode.hx | 2 +- .../leenkx/logicnode/PauseSoundNode.hx | 2 +- .../leenkx/logicnode/PauseTilesheetNode.hx | 5 +- .../leenkx/logicnode/PickLocationNode.hx | 87 +- .../leenkx/logicnode/PlayActionFromNode.hx | 6 +- .../leenkx/logicnode/PlayActionNode.hx | 2 +- .../Sources/leenkx/logicnode/PlaySoundNode.hx | 23 +- .../leenkx/logicnode/PlaySoundRawNode.hx | 59 +- .../logicnode/PlayTilesheetActionNode.hx | 5 +- .../leenkx/logicnode/PoissonDiskNode.hx | 33 + .../leenkx/logicnode/ProceduralNoiseNode.hx | 48 + .../leenkx/logicnode/RandomColorNode.hx | 24 +- .../leenkx/logicnode/RandomObjectNode.hx | 117 + .../logicnode/ReconstructNavMeshNode.hx | 23 + .../leenkx/logicnode/RemoveObjectNode.hx | 6 +- .../logicnode/RemoveParticleFromObjectNode.hx | 4 +- .../logicnode/RemovePhysicsConstraintNode.hx | 28 + .../leenkx/logicnode/RemovePhysicsHookNode.hx | 26 + .../leenkx/logicnode/RemovePhysicsNode.hx | 7 +- .../leenkx/logicnode/RemoveSoftBodyNode.hx | 40 + .../leenkx/logicnode/ReorderRender2DNode.hx | 20 + .../leenkx/logicnode/ResumeTilesheetNode.hx | 5 +- .../leenkx/logicnode/RetainValueNode.hx | 22 +- .../logicnode/SampleObjectPointsNode.hx | 85 + .../logicnode/SetActivationStateNode.hx | 2 +- .../leenkx/logicnode/SetAreaLightSizeNode.hx | 12 +- .../leenkx/logicnode/SetBoneFkIkOnlyNode.hx | 2 +- .../logicnode/SetCameraProjectionNode.hx | 22 + .../logicnode/SetCameraRenderFilterNode.hx | 31 +- .../leenkx/logicnode/SetCurveDataNode.hx | 29 + .../leenkx/logicnode/SetCurveMeshNode.hx | 76 + .../leenkx/logicnode/SetCurveShapeKeyNode.hx | 21 + .../leenkx/logicnode/SetCurveSplineNode.hx | 22 + .../leenkx/logicnode/SetDimensionNode.hx | 23 + .../leenkx/logicnode/SetFrictionNode.hx | 27 - .../logicnode/SetGlobalCanvasFontSizeNode.hx | 7 +- .../logicnode/SetGlobalCanvasScaleNode.hx | 7 +- .../SetGlobalCanvasVisibilityNode.hx | 12 +- .../leenkx/logicnode/SetGravityEnabledNode.hx | 5 +- .../leenkx/logicnode/SetHaxePropertyNode.hx | 2 +- .../leenkx/logicnode/SetLightColorNode.hx | 12 +- .../leenkx/logicnode/SetLightStrengthNode.hx | 11 +- .../logicnode/SetMaterialImageParamNode.hx | 8 +- .../leenkx/logicnode/SetMaterialNode.hx | 2 +- .../logicnode/SetMaterialRgbParamNode.hx | 8 +- .../leenkx/logicnode/SetMaterialSlotNode.hx | 4 +- .../logicnode/SetMaterialTextureFilterNode.hx | 4 +- .../logicnode/SetMaterialValueParamNode.hx | 8 +- .../leenkx/logicnode/SetMaterialsNode.hx | 23 + .../Sources/leenkx/logicnode/SetMeshNode.hx | 2 +- .../Sources/leenkx/logicnode/SetNameNode.hx | 2 +- .../logicnode/SetObjectInstancedNode.hx | 62 + .../leenkx/logicnode/SetObjectShapeKeyNode.hx | 6 +- .../leenkx/logicnode/SetParticleDataNode.hx | 2 +- .../logicnode/SetParticleRenderEmitterNode.hx | 2 +- .../leenkx/logicnode/SetParticleSpeedNode.hx | 4 +- .../leenkx/logicnode/SetPitchSoundNode.hx | 22 + .../logicnode/SetPositionSpeakerNode.hx | 19 +- .../leenkx/logicnode/SetPropertyNode.hx | 2 +- .../logicnode/SetRigidBodyAnimatedNode.hx | 35 + .../logicnode/SetRigidBodyBouncinessNode.hx | 29 + .../logicnode/SetRigidBodyDampingNode.hx | 31 + .../logicnode/SetRigidBodyDynamicsNode.hx | 43 + .../logicnode/SetRigidBodyFactorNode.hx | 37 + .../logicnode/SetRigidBodyFrictionNode.hx | 34 + .../logicnode/SetRigidBodyGravityNode.hx | 29 + .../logicnode/SetRigidBodyTriggerNode.hx | 33 + .../logicnode/SetRigidBodyVelocityNode.hx | 59 + .../leenkx/logicnode/SetRotationNode.hx | 28 +- .../leenkx/logicnode/SetScreenCamerasNode.hx | 66 + .../leenkx/logicnode/SetShaderUniformNode.hx | 29 +- .../leenkx/logicnode/SetSpotLightBlendNode.hx | 12 +- .../leenkx/logicnode/SetSpotLightSizeNode.hx | 14 +- .../logicnode/SetTilesheetActionNode.hx | 3 +- .../leenkx/logicnode/SetTilesheetFlipNode.hx | 6 +- .../leenkx/logicnode/SetTilesheetFrameNode.hx | 5 +- .../logicnode/SetTilesheetPausedNode.hx | 5 +- .../leenkx/logicnode/SetVelocityNode.hx | 33 - .../leenkx/logicnode/SetVirtualButtonNode.hx | 30 + .../leenkx/logicnode/SetVisibleNode.hx | 2 +- .../leenkx/logicnode/SetVolumeSoundNode.hx | 2 +- .../Sources/leenkx/logicnode/SetWorldNode.hx | 10 +- .../leenkx/logicnode/SetWorldProbeNode.hx | 28 + .../leenkx/logicnode/SetWorldStrengthNode.hx | 5 +- .../leenkx/logicnode/SpawnObjectByNameNode.hx | 17 +- .../leenkx/logicnode/SpawnObjectNode.hx | 8 +- .../leenkx/logicnode/SplitStringNode.hx | 2 +- .../Sources/leenkx/logicnode/StopAgentNode.hx | 16 +- .../Sources/leenkx/logicnode/StopCrowdNode.hx | 29 + .../Sources/leenkx/logicnode/StopSoundNode.hx | 2 +- leenkx/Sources/leenkx/logicnode/TimeNode.hx | 9 +- .../leenkx/logicnode/TransformMathNode.hx | 14 +- .../logicnode/TranslateOnLocalAxisNode.hx | 2 +- .../leenkx/logicnode/TweenFloatNode.hx | 12 + .../leenkx/logicnode/TweenRotationNode.hx | 12 + .../leenkx/logicnode/TweenTransformNode.hx | 12 + .../leenkx/logicnode/TweenVectorNode.hx | 12 + .../logicnode/WorldToScreenSpaceNode.hx | 4 +- .../Sources/leenkx/logicnode/WriteGifNode.hx | 185 ++ .../leenkx/logicnode/WriteImageNode.hx | 25 +- .../Sources/leenkx/logicnode/WriteJsonNode.hx | 3 +- leenkx/Sources/leenkx/math/Generator.hx | 464 ++++ .../Sources/leenkx/object/BreakerExtension.hx | 554 ++++ .../leenkx/object/MeshDataExtension.hx | 309 +++ .../renderpath/DynamicResolutionScale.hx | 77 +- leenkx/Sources/leenkx/renderpath/Inc.hx | 68 +- .../Sources/leenkx/renderpath/Postprocess.hx | 11 + .../leenkx/renderpath/RenderPathForward.hx | 8 + leenkx/Sources/leenkx/system/Starter.hx | 3 + leenkx/Sources/leenkx/trait/Character.hx | 4 +- leenkx/Sources/leenkx/trait/CustomParticle.hx | 10 - .../leenkx/trait/FirstPersonController.hx | 24 +- leenkx/Sources/leenkx/trait/FollowCurve.hx | 64 + .../leenkx/trait/KinematicController.hx | 89 + leenkx/Sources/leenkx/trait/MeshBuild.hx | 131 + leenkx/Sources/leenkx/trait/MeshGenerator.hx | 173 ++ leenkx/Sources/leenkx/trait/MeshRenderDraw.hx | 52 + leenkx/Sources/leenkx/trait/NavAgent.hx | 173 +- leenkx/Sources/leenkx/trait/NavBoxObstacle.hx | 65 + leenkx/Sources/leenkx/trait/NavCrowd.hx | 85 +- ...{NavObstacle.hx => NavCylinderObstacle.hx} | 32 +- leenkx/Sources/leenkx/trait/NavMesh.hx | 173 +- leenkx/Sources/leenkx/trait/PhysicsBreak.hx | 825 +----- leenkx/Sources/leenkx/trait/PhysicsDrag.hx | 42 +- .../Sources/leenkx/trait/SoundController.hx | 113 + .../leenkx/trait/internal/DebugConsole.hx | 124 +- .../leenkx/trait/internal/DebugDraw.hx | 2 +- .../leenkx/trait/internal/LoadingScreen.hx | 39 +- .../leenkx/trait/internal/RenderDraw.hx | 574 ++++ .../leenkx/trait/internal/SoundGenerator.hx | 474 ++++ .../Sources/leenkx/trait/internal/Spectral.hx | 101 + .../leenkx/trait/internal/UniformsManager.hx | 44 +- .../trait/navigation/DebugDrawHelper.hx | 42 + .../leenkx/trait/navigation/Navigation.hx | 3 +- .../trait/physics/bullet/DebugDrawHelper.hx | 2 +- .../bullet/KinematicCharacterController.hx | 4 +- .../trait/physics/bullet/PhysicsConstraint.hx | 2 - .../trait/physics/bullet/PhysicsHook.hx | 71 +- .../trait/physics/bullet/PhysicsWorld.hx | 44 +- .../leenkx/trait/physics/bullet/RigidBody.hx | 12 +- .../leenkx/trait/physics/bullet/SoftBody.hx | 78 +- .../leenkx/trait/physics/jolt/RigidBody.hx | 4 + leenkx/Sources/leenkx/ui/Canvas.hx | 10 +- leenkx/Sources/zui/Ext.hx | 2 +- leenkx/Sources/zui/Zui.hx | 31 +- leenkx/blender/lnx/exporter.py | 272 +- leenkx/blender/lnx/exporter_opt.py | 2 +- leenkx/blender/lnx/handlers.py | 2 +- leenkx/blender/lnx/keymap.py | 10 +- leenkx/blender/lnx/lib/make_datas.py | 12 + leenkx/blender/lnx/logicnode/__init__.py | 1 + .../lnx/logicnode/animation/LN_get_actions.py | 13 + .../array/LN_async_array_loop_node.py | 18 + .../camera/LN_set_camera_projection.py | 15 + .../camera/LN_set_camera_start_end.py | 4 +- .../logicnode/camera/LN_set_screen_cameras.py | 60 + .../lnx/logicnode/curve/LN_along_curve.py | 17 + .../lnx/logicnode/curve/LN_deform_curve.py | 18 + .../lnx/logicnode/curve/LN_follow_curve.py | 22 + .../lnx/logicnode/curve/LN_get_curve_data.py | 18 + .../logicnode/curve/LN_get_curve_spline.py | 16 + .../lnx/logicnode/curve/LN_set_curve_data.py | 38 + .../lnx/logicnode/curve/LN_set_curve_mesh.py | 52 + .../logicnode/curve/LN_set_curve_shape_key.py | 16 + .../logicnode/curve/LN_set_curve_spline.py | 17 + .../blender/lnx/logicnode/curve/__init__.py | 3 + .../lnx/logicnode/draw/LN_draw_camera.py | 39 +- .../blender/lnx/logicnode/draw/LN_draw_gif.py | 49 + .../logicnode/draw/LN_draw_image_render.py | 1 + .../lnx/logicnode/draw/LN_draw_string.py | 18 + .../lnx/logicnode/draw/LN_reorder_render2D.py | 17 + .../lnx/logicnode/event/LN_on_render2d.py | 13 +- .../logicnode/input/LN_get_gamepad_started.py | 1 + .../lnx/logicnode/input/LN_get_image_color.py | 45 + .../lnx/logicnode/input/LN_get_keyboard.py | 30 + .../input/LN_get_keyboard_started.py | 1 + .../logicnode/input/LN_get_mouse_started.py | 1 + .../lnx/logicnode/input/LN_keyboard.py | 2 +- .../blender/lnx/logicnode/input/LN_mouse.py | 2 +- .../logicnode/input/LN_set_virtual_button.py | 26 + .../blender/lnx/logicnode/input/LN_touch.py | 2 +- .../logicnode/light/LN_get_area_light_data.py | 13 + .../lnx/logicnode/light/LN_get_light_data.py | 15 + .../logicnode/light/LN_get_spot_light_data.py | 13 + leenkx/blender/lnx/logicnode/lnx_sockets.py | 2 +- .../lnx/logicnode/logic/LN_async_loop.py | 42 + .../lnx/logicnode/logic/LN_call_function.py | 2 +- .../logic/LN_set_camera_render_filter.py | 2 + .../material/LN_get_material_image_param.py | 31 + .../material/LN_get_material_rgb_param.py | 31 + .../material/LN_get_material_value_param.py | 31 + .../material/LN_set_object_materials.py | 14 + .../lnx/logicnode/math/LN_colors_mix.py | 16 + leenkx/blender/lnx/logicnode/math/LN_math.py | 10 +- .../lnx/logicnode/math/LN_matrix_math.py | 22 - .../lnx/logicnode/math/LN_poisson_disk.py | 54 + .../lnx/logicnode/math/LN_procedural_noise.py | 71 + .../lnx/logicnode/math/LN_tween_float.py | 8 +- .../lnx/logicnode/math/LN_tween_rotation.py | 8 +- .../lnx/logicnode/math/LN_tween_transform.py | 8 +- .../lnx/logicnode/math/LN_tween_vector.py | 8 +- .../math/LN_world_to_screen_space.py | 6 +- leenkx/blender/lnx/logicnode/math/__init__.py | 1 + .../miscellaneous/LN_get_application_time.py | 11 +- .../LN_get_debug_console_settings.py | 3 +- .../LN_set_debug_console_settings.py | 3 +- .../miscellaneous/LN_set_time_scale.py | 1 + .../lnx/logicnode/miscellaneous/LN_sleep.py | 1 + .../lnx/logicnode/miscellaneous/LN_timer.py | 1 + .../lnx/logicnode/native/LN_get_assets.py | 11 + .../lnx/logicnode/native/LN_write_gif.py | 43 + .../navmesh/LN_around_navigable_location.py | 14 + .../navmesh/LN_crowd_go_to_location.py | 9 + .../navmesh/LN_crowd_set_location.py | 21 + .../logicnode/navmesh/LN_get_agent_data.py | 7 +- .../logicnode/navmesh/LN_get_crowd_data.py | 21 + .../logicnode/navmesh/LN_go_to_location.py | 17 +- .../navmesh/LN_navigable_location.py | 10 +- .../navmesh/LN_pick_navmesh_location.py | 7 +- .../navmesh/LN_reconstruct_navmesh.py | 13 + .../lnx/logicnode/navmesh/LN_stop_agent.py | 1 + .../lnx/logicnode/navmesh/LN_stop_crowd.py | 14 + .../lnx/logicnode/object/LN_convex_break.py | 39 + .../object/LN_get_object_camera_data.py | 14 + .../logicnode/object/LN_get_object_child.py | 24 +- .../object/LN_get_object_children.py | 1 + .../logicnode/object/LN_get_object_geom.py | 15 +- .../object/LN_get_object_instanced.py | 16 + .../object/LN_get_object_shape_key.py | 14 + .../object/LN_sample_object_points.py | 16 + .../object/LN_set_object_instanced.py | 27 + .../logicnode/object/LN_set_object_parent.py | 2 +- .../logicnode/object/LN_set_object_visible.py | 2 +- .../lnx/logicnode/particle/LN_curve_guide.py | 17 + .../lnx/logicnode/particle/LN_get_particle.py | 2 +- .../particle/LN_get_particle_data.py | 1 + .../logicnode/physics/LN_Add_rigid_body.py | 7 +- .../logicnode/physics/LN_add_physics_hook.py | 14 + .../lnx/logicnode/physics/LN_add_soft_body.py | 23 + .../lnx/logicnode/physics/LN_get_rb_data.py | 32 +- .../logicnode/physics/LN_get_rb_dynamics.py | 14 + .../physics/LN_get_rb_point_velocity.py | 4 +- .../lnx/logicnode/physics/LN_get_sb_data.py | 22 + .../logicnode/physics/LN_on_contact_array.py | 2 + .../physics/LN_remove_physics_constraint.py | 13 + .../physics/LN_remove_physics_hook.py | 12 + .../lnx/logicnode/physics/LN_remove_sb.py | 14 + .../physics/LN_set_rb_activation_state.py | 1 - .../logicnode/physics/LN_set_rb_animated.py | 13 + .../logicnode/physics/LN_set_rb_bounciness.py | 13 + .../logicnode/physics/LN_set_rb_damping.py | 15 + .../lnx/logicnode/physics/LN_set_rb_factor.py | 14 + .../logicnode/physics/LN_set_rb_friction.py | 15 +- .../logicnode/physics/LN_set_rb_gravity.py | 14 + .../logicnode/physics/LN_set_rb_trigger.py | 13 + .../logicnode/physics/LN_set_rb_velocity.py | 27 +- .../postprocess/LN_get_camera_post_process.py | 2 + .../postprocess/LN_set_camera_post_process.py | 6 + .../lnx/logicnode/random/LN_random_color.py | 83 +- .../lnx/logicnode/random/LN_random_object.py | 25 + .../renderpath/LN_set_shader_uniform.py | 8 +- .../lnx/logicnode/scene/LN_collection.py | 6 +- .../lnx/logicnode/scene/LN_get_collection.py | 6 +- .../scene/LN_get_object_collection.py | 4 +- .../scene/LN_get_scene_active_object.py | 10 + .../sound/LN_get_position_speaker.py | 11 - .../logicnode/sound/LN_get_speaker_data.py | 16 + .../lnx/logicnode/sound/LN_play_sound.py | 55 +- .../lnx/logicnode/sound/LN_play_speaker.py | 4 + .../logicnode/sound/LN_set_pitch_speaker.py | 18 + .../sound/LN_set_position_speaker.py | 2 +- .../lnx/logicnode/string/LN_format_number.py | 14 + .../lnx/logicnode/string/LN_format_time.py | 12 + .../lnx/logicnode/string/LN_split_string.py | 10 +- .../logicnode/trait/LN_add_trait_to_object.py | 28 +- .../logicnode/trait/LN_get_object_traits.py | 6 +- .../transform/LN_get_object_transform.py | 9 +- .../logicnode/transform/LN_rotate_object.py | 6 - .../transform/LN_separate_rotation.py | 7 +- .../transform/LN_set_object_dimension.py | 15 + .../transform/LN_set_object_rotation.py | 41 +- .../logicnode/transform/LN_transform_math.py | 22 +- .../logicnode/variable/LN_math_constants.py | 12 + .../lnx/logicnode/variable/LN_rotation.py | 11 +- .../lnx/logicnode/world/LN_get_world_data.py | 12 + .../lnx/logicnode/world/LN_set_world_probe.py | 15 + leenkx/blender/lnx/make.py | 70 +- leenkx/blender/lnx/make_logic.py | 34 +- leenkx/blender/lnx/make_renderpath.py | 4 + leenkx/blender/lnx/material/cycles.py | 78 +- .../blender/lnx/material/cycles_functions.py | 2444 ++++++++++++++++- .../lnx/material/cycles_nodes/nodes_color.py | 36 +- .../material/cycles_nodes/nodes_converter.py | 63 +- .../lnx/material/cycles_nodes/nodes_input.py | 74 +- .../material/cycles_nodes/nodes_texture.py | 309 ++- .../lnx/material/cycles_nodes/nodes_vector.py | 229 +- .../material/lnx_nodes/shader_data_node.py | 53 +- leenkx/blender/lnx/material/make.py | 2 +- leenkx/blender/lnx/material/make_attrib.py | 4 +- leenkx/blender/lnx/material/make_depth.py | 6 +- leenkx/blender/lnx/material/make_finalize.py | 45 +- leenkx/blender/lnx/material/make_inst.py | 12 +- leenkx/blender/lnx/material/make_mesh.py | 10 +- .../blender/lnx/material/make_morph_target.py | 14 +- leenkx/blender/lnx/material/make_particle.py | 67 +- leenkx/blender/lnx/material/make_shader.py | 6 + leenkx/blender/lnx/material/node_meta.py | 3 + leenkx/blender/lnx/material/shader.py | 16 +- leenkx/blender/lnx/nodes_logic.py | 10 +- leenkx/blender/lnx/props.py | 33 +- leenkx/blender/lnx/props_action.py | 14 +- leenkx/blender/lnx/props_properties.py | 217 +- leenkx/blender/lnx/props_renderpath.py | 4 +- leenkx/blender/lnx/props_tilesheet.py | 2 +- leenkx/blender/lnx/props_traits.py | 5 +- leenkx/blender/lnx/props_ui.py | 142 +- leenkx/blender/lnx/utils.py | 35 +- leenkx/blender/lnx/write_data.py | 59 +- lib/hscript/.github/workflows/main.yml | 43 + lib/hscript/.gitignore | 2 + lib/hscript/.vscode/launch.json | 16 + lib/hscript/.vscode/tasks.json | 16 + lib/hscript/LICENSE | 22 + lib/hscript/README.md | 100 + lib/hscript/TestHScript.hx | 263 ++ lib/hscript/bin/.gitignore | 2 + lib/hscript/extraParams.hxml | 1 + lib/hscript/haxelib.json | 9 + lib/hscript/hscript.hxml | 2 + lib/hscript/hscript/Async.hx | 536 ++++ lib/hscript/hscript/Bytes.hx | 518 ++++ lib/hscript/hscript/Checker.hx | 1618 +++++++++++ lib/hscript/hscript/Expr.hx | 180 ++ lib/hscript/hscript/Interp.hx | 745 +++++ lib/hscript/hscript/JsInterp.hx | 448 +++ lib/hscript/hscript/Macro.hx | 262 ++ lib/hscript/hscript/Parser.hx | 1801 ++++++++++++ lib/hscript/hscript/Printer.hx | 374 +++ lib/hscript/hscript/Tools.hx | 133 + lib/hscript/release.bat | 20 + lib/hscript/script/RunScript.hx | 154 ++ lib/hscript/script/build.hxml | 4 + 482 files changed, 27797 insertions(+), 3226 deletions(-) create mode 100644 leenkx/Shaders/add_pass/add_pass.json create mode 100644 leenkx/Shaders/render_draw/render_line.frag.glsl create mode 100644 leenkx/Shaders/render_draw/render_line.vert.glsl create mode 100644 leenkx/Shaders/render_draw/render_line_deferred.frag.glsl create mode 100644 leenkx/Sources/iron/format/gif/Data.hx create mode 100644 leenkx/Sources/iron/format/gif/GifEncoder.hx create mode 100644 leenkx/Sources/iron/format/gif/LzwEncoder.hx create mode 100644 leenkx/Sources/iron/format/gif/NeuQuant.hx create mode 100644 leenkx/Sources/iron/format/gif/Reader.hx create mode 100644 leenkx/Sources/iron/format/gif/Tools.hx create mode 100644 leenkx/Sources/iron/format/gif/Writer.hx create mode 100644 leenkx/Sources/iron/format/jpg/Data.hx create mode 100644 leenkx/Sources/iron/format/jpg/Writer.hx create mode 100644 leenkx/Sources/iron/format/jpg/WriterOriginal.hx create mode 100644 leenkx/Sources/iron/format/wav/Data.hx create mode 100644 leenkx/Sources/iron/format/wav/Reader.hx create mode 100644 leenkx/Sources/iron/format/wav/Writer.hx create mode 100644 leenkx/Sources/iron/object/CurveObject.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ActiveSceneObjectNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AddPhysicsHookNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AddSoftBodyNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AlongCurveNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AnimationNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AroundNavigableLocationNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AsyncArrayLoopNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/AsyncLoopNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ColorsMixNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ConvexBreakNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/CrowdSetLocationNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/CurveGuideNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/DeformCurveNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/DrawGifNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/FollowCurveNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/FormatNumberNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/FormatTimeNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetAreaLightDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetAssetsNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetCrowdDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetCurveDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetCurveSplineNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetKeyboardNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetLightDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetMaterialImageParamNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetMaterialRgbParamNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetMaterialValueParamNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetObjectCameraDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetObjectInstancedNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetObjectShapeKeyNode.hx delete mode 100644 leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetSoftBodyDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetSpeakerDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetSpotLightDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/GetWorldDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/MathConstantsNode.hx delete mode 100644 leenkx/Sources/leenkx/logicnode/MatrixMathNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/PoissonDiskNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ProceduralNoiseNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/RandomObjectNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ReconstructNavMeshNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/RemovePhysicsConstraintNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/RemovePhysicsHookNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/RemoveSoftBodyNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/ReorderRender2DNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SampleObjectPointsNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetCameraProjectionNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetCurveDataNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetCurveMeshNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetCurveShapeKeyNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetCurveSplineNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetDimensionNode.hx delete mode 100644 leenkx/Sources/leenkx/logicnode/SetFrictionNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetMaterialsNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetObjectInstancedNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetPitchSoundNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyAnimatedNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyBouncinessNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyDampingNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyDynamicsNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyFactorNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyFrictionNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyGravityNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyTriggerNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetRigidBodyVelocityNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetScreenCamerasNode.hx delete mode 100644 leenkx/Sources/leenkx/logicnode/SetVelocityNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetVirtualButtonNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/SetWorldProbeNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/StopCrowdNode.hx create mode 100644 leenkx/Sources/leenkx/logicnode/WriteGifNode.hx create mode 100644 leenkx/Sources/leenkx/math/Generator.hx create mode 100644 leenkx/Sources/leenkx/object/BreakerExtension.hx create mode 100644 leenkx/Sources/leenkx/object/MeshDataExtension.hx create mode 100644 leenkx/Sources/leenkx/trait/FollowCurve.hx create mode 100644 leenkx/Sources/leenkx/trait/KinematicController.hx create mode 100644 leenkx/Sources/leenkx/trait/MeshBuild.hx create mode 100644 leenkx/Sources/leenkx/trait/MeshGenerator.hx create mode 100644 leenkx/Sources/leenkx/trait/MeshRenderDraw.hx create mode 100644 leenkx/Sources/leenkx/trait/NavBoxObstacle.hx rename leenkx/Sources/leenkx/trait/{NavObstacle.hx => NavCylinderObstacle.hx} (56%) create mode 100644 leenkx/Sources/leenkx/trait/SoundController.hx create mode 100644 leenkx/Sources/leenkx/trait/internal/RenderDraw.hx create mode 100644 leenkx/Sources/leenkx/trait/internal/SoundGenerator.hx create mode 100644 leenkx/Sources/leenkx/trait/internal/Spectral.hx create mode 100644 leenkx/blender/lnx/logicnode/animation/LN_get_actions.py create mode 100644 leenkx/blender/lnx/logicnode/array/LN_async_array_loop_node.py create mode 100644 leenkx/blender/lnx/logicnode/camera/LN_set_camera_projection.py create mode 100644 leenkx/blender/lnx/logicnode/camera/LN_set_screen_cameras.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_along_curve.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_deform_curve.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_follow_curve.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_get_curve_data.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_get_curve_spline.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_set_curve_data.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_set_curve_mesh.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_set_curve_shape_key.py create mode 100644 leenkx/blender/lnx/logicnode/curve/LN_set_curve_spline.py create mode 100644 leenkx/blender/lnx/logicnode/curve/__init__.py create mode 100644 leenkx/blender/lnx/logicnode/draw/LN_draw_gif.py create mode 100644 leenkx/blender/lnx/logicnode/draw/LN_reorder_render2D.py create mode 100644 leenkx/blender/lnx/logicnode/input/LN_get_image_color.py create mode 100644 leenkx/blender/lnx/logicnode/input/LN_get_keyboard.py create mode 100644 leenkx/blender/lnx/logicnode/input/LN_set_virtual_button.py create mode 100644 leenkx/blender/lnx/logicnode/light/LN_get_area_light_data.py create mode 100644 leenkx/blender/lnx/logicnode/light/LN_get_light_data.py create mode 100644 leenkx/blender/lnx/logicnode/light/LN_get_spot_light_data.py create mode 100644 leenkx/blender/lnx/logicnode/logic/LN_async_loop.py create mode 100644 leenkx/blender/lnx/logicnode/material/LN_get_material_image_param.py create mode 100644 leenkx/blender/lnx/logicnode/material/LN_get_material_rgb_param.py create mode 100644 leenkx/blender/lnx/logicnode/material/LN_get_material_value_param.py create mode 100644 leenkx/blender/lnx/logicnode/material/LN_set_object_materials.py create mode 100644 leenkx/blender/lnx/logicnode/math/LN_colors_mix.py delete mode 100644 leenkx/blender/lnx/logicnode/math/LN_matrix_math.py create mode 100644 leenkx/blender/lnx/logicnode/math/LN_poisson_disk.py create mode 100644 leenkx/blender/lnx/logicnode/math/LN_procedural_noise.py create mode 100644 leenkx/blender/lnx/logicnode/native/LN_get_assets.py create mode 100644 leenkx/blender/lnx/logicnode/native/LN_write_gif.py create mode 100644 leenkx/blender/lnx/logicnode/navmesh/LN_around_navigable_location.py create mode 100644 leenkx/blender/lnx/logicnode/navmesh/LN_crowd_set_location.py create mode 100644 leenkx/blender/lnx/logicnode/navmesh/LN_get_crowd_data.py create mode 100644 leenkx/blender/lnx/logicnode/navmesh/LN_reconstruct_navmesh.py create mode 100644 leenkx/blender/lnx/logicnode/navmesh/LN_stop_crowd.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_convex_break.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_get_object_camera_data.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_get_object_instanced.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_get_object_shape_key.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_sample_object_points.py create mode 100644 leenkx/blender/lnx/logicnode/object/LN_set_object_instanced.py create mode 100644 leenkx/blender/lnx/logicnode/particle/LN_curve_guide.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_add_physics_hook.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_add_soft_body.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_get_rb_dynamics.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_get_sb_data.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_remove_physics_constraint.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_remove_physics_hook.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_remove_sb.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_animated.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_bounciness.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_damping.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_factor.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_gravity.py create mode 100644 leenkx/blender/lnx/logicnode/physics/LN_set_rb_trigger.py create mode 100644 leenkx/blender/lnx/logicnode/random/LN_random_object.py create mode 100644 leenkx/blender/lnx/logicnode/scene/LN_get_scene_active_object.py delete mode 100644 leenkx/blender/lnx/logicnode/sound/LN_get_position_speaker.py create mode 100644 leenkx/blender/lnx/logicnode/sound/LN_get_speaker_data.py create mode 100644 leenkx/blender/lnx/logicnode/sound/LN_set_pitch_speaker.py create mode 100644 leenkx/blender/lnx/logicnode/string/LN_format_number.py create mode 100644 leenkx/blender/lnx/logicnode/string/LN_format_time.py create mode 100644 leenkx/blender/lnx/logicnode/transform/LN_set_object_dimension.py create mode 100644 leenkx/blender/lnx/logicnode/variable/LN_math_constants.py create mode 100644 leenkx/blender/lnx/logicnode/world/LN_get_world_data.py create mode 100644 leenkx/blender/lnx/logicnode/world/LN_set_world_probe.py create mode 100644 lib/hscript/.github/workflows/main.yml create mode 100644 lib/hscript/.gitignore create mode 100644 lib/hscript/.vscode/launch.json create mode 100644 lib/hscript/.vscode/tasks.json create mode 100644 lib/hscript/LICENSE create mode 100644 lib/hscript/README.md create mode 100644 lib/hscript/TestHScript.hx create mode 100644 lib/hscript/bin/.gitignore create mode 100644 lib/hscript/extraParams.hxml create mode 100644 lib/hscript/haxelib.json create mode 100644 lib/hscript/hscript.hxml create mode 100644 lib/hscript/hscript/Async.hx create mode 100644 lib/hscript/hscript/Bytes.hx create mode 100644 lib/hscript/hscript/Checker.hx create mode 100644 lib/hscript/hscript/Expr.hx create mode 100644 lib/hscript/hscript/Interp.hx create mode 100644 lib/hscript/hscript/JsInterp.hx create mode 100644 lib/hscript/hscript/Macro.hx create mode 100644 lib/hscript/hscript/Parser.hx create mode 100644 lib/hscript/hscript/Printer.hx create mode 100644 lib/hscript/hscript/Tools.hx create mode 100644 lib/hscript/release.bat create mode 100644 lib/hscript/script/RunScript.hx create mode 100644 lib/hscript/script/build.hxml diff --git a/leenkx/Shaders/add_pass/add_pass.json b/leenkx/Shaders/add_pass/add_pass.json new file mode 100644 index 00000000..fff2f969 --- /dev/null +++ b/leenkx/Shaders/add_pass/add_pass.json @@ -0,0 +1,17 @@ +{ + "contexts": [ + { + "name": "add_pass", + "depth_write": false, + "compare_mode": "always", + "cull_mode": "none", + "blend_source": "source_alpha", + "blend_destination": "inverse_source_alpha", + "blend_operation": "add", + "links": [], + "texture_params": [], + "vertex_shader": "../include/pass.vert.glsl", + "fragment_shader": "../include/pass_copy.frag.glsl" + } + ] +} diff --git a/leenkx/Shaders/compositor_pass/compositor_pass.frag.glsl b/leenkx/Shaders/compositor_pass/compositor_pass.frag.glsl index 7413a5be..8118432e 100644 --- a/leenkx/Shaders/compositor_pass/compositor_pass.frag.glsl +++ b/leenkx/Shaders/compositor_pass/compositor_pass.frag.glsl @@ -11,6 +11,7 @@ #endif uniform sampler2D tex; + #ifdef _CDepth uniform sampler2D gbufferD; #endif @@ -67,6 +68,7 @@ uniform vec3 PPComp14; uniform vec4 PPComp15; uniform vec4 PPComp16; uniform vec4 PPComp18; +uniform vec4 PPComp19; #endif // #ifdef _CPos @@ -230,6 +232,45 @@ vec3 lensflare(vec2 uv, vec2 pos) { } #endif +#ifdef _CDistort +float distortHash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float distortValueNoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + vec2 u = f * f * (3.0 - 2.0 * f); + + float a = distortHash(i); + float b = distortHash(i + vec2(1.0, 0.0)); + float c = distortHash(i + vec2(0.0, 1.0)); + float d = distortHash(i + vec2(1.0, 1.0)); + + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} + +vec2 distortSmoothNoise(vec2 p) { + return vec2( + distortValueNoise(p), + distortValueNoise(p + vec2(5.2, 1.3)) + ); +} + +vec2 distortUV(vec2 uv, vec2 nUV, float t, float strength) { + float intensity = 0.01 * strength; + float scale = 4.0; + float speed = 0.25; + + nUV.x += t * speed; + nUV.y += t * speed; + vec2 noise = distortSmoothNoise(nUV * scale); + + uv += (-1.0 + noise * 2.0) * intensity; + return uv; +} +#endif + void main() { vec2 texCo = texCoord; #ifdef _DynRes @@ -252,22 +293,25 @@ void main() { #ifdef _CFishEye #ifdef _CPostprocess - const float fishEyeStrength = -(PPComp2.y); + float fishEyeStrength = PPComp2.y; #else - const float fishEyeStrength = -0.01; + float fishEyeStrength = compoFisheyeStrength; #endif - const vec2 m = vec2(0.5, 0.5); - vec2 d = texCo - m; - float r = sqrt(dot(d, d)); - float power = (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength; - float bind; - if (power > 0.0) { bind = sqrt(dot(m, m)); } - else { bind = m.x; } - if (power > 0.0) { - texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power); - } - else { - texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0); + + if (abs(fishEyeStrength) > 0.0001) { + const vec2 m = vec2(0.5, 0.5); + vec2 d = texCo - m; + float r = sqrt(dot(d, d)); + float power = - (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength; + float bind; + if (power > 0.0) { bind = sqrt(dot(m, m)); } + else { bind = m.x; } + if (power > 0.0) { + texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power); + } + else { + texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0); + } } #endif @@ -277,9 +321,28 @@ void main() { #else float strengthDistort = compoDistortStrength; #endif - float uX = time * strengthDistort; - texCo.y = texCo.y + (sin(texCo.x*4.0+uX*2.0)*0.01); - texCo.x = texCo.x + (cos(texCo.y*4.0+uX*2.0)*0.01); + + vec2 nUV = texCo; + + texCo = distortUV(texCo, nUV, time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.1, nUV.y + 0.1), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.2, nUV.y + 0.2), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.3, nUV.y + 0.3), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.4, nUV.y + 0.4), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.5, nUV.y + 0.5), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.6, nUV.y + 0.6), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.7, nUV.y + 0.7), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.8, nUV.y + 0.8), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.9, nUV.y + 0.9), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.15, nUV.y + 0.15), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.25, nUV.y + 0.25), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.35, nUV.y + 0.35), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.45, nUV.y + 0.45), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.55, nUV.y + 0.55), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.65, nUV.y + 0.65), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.75, nUV.y + 0.75), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.85, nUV.y + 0.85), time, strengthDistort); + texCo = distortUV(texCo, vec2(nUV.x + 0.95, nUV.y + 0.95), time, strengthDistort); #endif #ifdef _CDepth @@ -343,6 +406,7 @@ void main() { float compoDistance = PPComp3.x; float compoLength = PPComp3.y; float compoStop = PPComp3.z; + vec2 focus = vec2(PPComp19.x, PPComp19.y); if (PPComp2.z == 1){ compoAutoFocus = true; @@ -350,9 +414,9 @@ void main() { compoAutoFocus = false; } - fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop); + fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop, focus, PPComp19.z); #else - fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop); + fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop, vec2(0.5, 0.5), 1.0); #endif #else fragColor = textureLod(tex, texCo, 0.0); @@ -382,8 +446,10 @@ void main() { vec3 col4 = textureLod(tex, texCo + vec2(texStep.x, texStep.y) * SharpenSize, 0.0).rgb; vec3 colavg = (col1 + col2 + col3 + col4) * 0.25; - float edgeMagnitude = length(fragColor.rgb - colavg); - fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0)); + float edgeMagnitude = length(fragColor.rgb - colavg); + float luma = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); + float sharpenMask = 1.0 - smoothstep(0.5, 0.8, luma); + fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0) * sharpenMask); #endif #ifdef _CFog diff --git a/leenkx/Shaders/compositor_pass/compositor_pass.json b/leenkx/Shaders/compositor_pass/compositor_pass.json index a1a74bcf..4e3e087a 100644 --- a/leenkx/Shaders/compositor_pass/compositor_pass.json +++ b/leenkx/Shaders/compositor_pass/compositor_pass.json @@ -245,6 +245,11 @@ "name": "PPComp18", "link": "_PPComp18", "ifdef": ["_CPostprocess"] + }, + { + "name": "PPComp19", + "link": "_PPComp19", + "ifdef": ["_CPostprocess"] } ], "texture_params": [], diff --git a/leenkx/Shaders/debug_draw/line_deferred.frag.glsl b/leenkx/Shaders/debug_draw/line_deferred.frag.glsl index 717c6875..cf2bd5e0 100644 --- a/leenkx/Shaders/debug_draw/line_deferred.frag.glsl +++ b/leenkx/Shaders/debug_draw/line_deferred.frag.glsl @@ -14,6 +14,6 @@ void main() { #endif #ifdef _EmissionShaded - fragColor[GBUF_IDX_EMISSION] = vec4(0.0); + fragColor[GBUF_IDX_EMISSION] = vec4(color, 1.0); #endif } diff --git a/leenkx/Shaders/render_draw/render_line.frag.glsl b/leenkx/Shaders/render_draw/render_line.frag.glsl new file mode 100644 index 00000000..213d44b3 --- /dev/null +++ b/leenkx/Shaders/render_draw/render_line.frag.glsl @@ -0,0 +1,8 @@ +#version 450 + +in vec4 color; +out vec4 fragColor; + +void main() { + fragColor = vec4(color); +} diff --git a/leenkx/Shaders/render_draw/render_line.vert.glsl b/leenkx/Shaders/render_draw/render_line.vert.glsl new file mode 100644 index 00000000..cffeaccb --- /dev/null +++ b/leenkx/Shaders/render_draw/render_line.vert.glsl @@ -0,0 +1,12 @@ +#version 450 + +in vec3 pos; +in vec4 col; + +uniform mat4 ViewProjection; +out vec4 color; + +void main() { + color = col; + gl_Position = ViewProjection * vec4(pos, 1.0); +} diff --git a/leenkx/Shaders/render_draw/render_line_deferred.frag.glsl b/leenkx/Shaders/render_draw/render_line_deferred.frag.glsl new file mode 100644 index 00000000..c7515152 --- /dev/null +++ b/leenkx/Shaders/render_draw/render_line_deferred.frag.glsl @@ -0,0 +1,15 @@ +#version 450 + +#include "compiled.inc" + +in vec4 color; +out vec4 fragColor[GBUF_SIZE]; + +void main() { + fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0); + fragColor[GBUF_IDX_1] = vec4(color); + + #ifdef _EmissionShaded + fragColor[GBUF_IDX_EMISSION] = vec4(color); + #endif +} diff --git a/leenkx/Shaders/std/dof.glsl b/leenkx/Shaders/std/dof.glsl index 9bd58924..1a9ded74 100644 --- a/leenkx/Shaders/std/dof.glsl +++ b/leenkx/Shaders/std/dof.glsl @@ -10,9 +10,9 @@ const int samples = 8; // Samples on the first ring const int rings = 6; // Ring count -const vec2 focus = vec2(0.5, 0.5); +//const vec2 focus = vec2(0.5, 0.5); const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm) -const float maxblur = 1.0; +//const float maxblur = 1.0; const float threshold = 0.5; // Highlight threshold const float gain = 2.0; // Highlight gain const float bias = 0.5; // Bokeh edge bias @@ -41,7 +41,9 @@ vec3 dof( const bool autoFocus, const float DOFDistance, const float DOFLength, - const float DOFFStop) { + const float DOFFStop, + const vec2 focus, + const float maxblur) { float depth = linearize(gdepth, cameraProj); float fDepth = 0.0; @@ -85,7 +87,6 @@ vec3 dof( float pw = (cos(float(j) * step) * float(i)); float ph = (sin(float(j) * step) * float(i)); float p = 1.0; - // if (pentagon) p = penta(vec2(pw, ph)); blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p; s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p; } diff --git a/leenkx/Shaders/std/light.glsl b/leenkx/Shaders/std/light.glsl index b093a3cb..53b2d4a3 100644 --- a/leenkx/Shaders/std/light.glsl +++ b/leenkx/Shaders/std/light.glsl @@ -28,15 +28,14 @@ #ifdef _ShadowMap #ifdef _SinglePoint - #ifdef _Spot - #ifndef _LTC - uniform sampler2DShadow shadowMapSpot[1]; - #ifdef _ShadowMapTransparent - uniform sampler2D shadowMapSpotTransparent[1]; - #endif - uniform mat4 LWVPSpotArray[1]; + #if defined(_Spot) || defined(_LTC) + uniform sampler2DShadow shadowMapSpot[1]; + #ifdef _ShadowMapTransparent + uniform sampler2D shadowMapSpotTransparent[1]; #endif - #else + uniform mat4 LWVPSpotArray[1]; + #endif + #ifndef _Spot uniform samplerCubeShadow shadowMapPoint[1]; #ifdef _ShadowMapTransparent uniform samplerCube shadowMapPointTransparent[1]; @@ -45,41 +44,35 @@ #endif #endif #ifdef _Clusters - #ifdef _SingleAtlas - //!uniform sampler2DShadow shadowMapAtlas; - #ifdef _ShadowMapTransparent - //!uniform sampler2D shadowMapAtlasTransparent; - #endif - #endif #ifndef _SinglePoint uniform vec2 lightProj; #endif #ifdef _ShadowMapAtlas - #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasPoint; - #ifdef _ShadowMapTransparent - uniform sampler2D shadowMapAtlasPointTransparent; - #endif - #endif - #else - uniform samplerCubeShadow shadowMapPoint[4]; - #ifdef _ShadowMapTransparent - uniform samplerCube shadowMapPointTransparent[4]; - #endif - #endif - #ifdef _Spot - #ifdef _ShadowMapAtlas #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasSpot; + uniform sampler2DShadow shadowMapAtlasPoint; #ifdef _ShadowMapTransparent - uniform sampler2D shadowMapAtlasSpotTransparent; + uniform sampler2D shadowMapAtlasPointTransparent; #endif #endif + #else + uniform samplerCubeShadow shadowMapPoint[4]; + #ifdef _ShadowMapTransparent + uniform samplerCube shadowMapPointTransparent[4]; + #endif + #endif + #if defined(_Spot) || defined(_LTC) + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #ifdef _ShadowMapTransparent + uniform sampler2D shadowMapAtlasSpotTransparent; + #endif + #endif #else - uniform sampler2DShadow shadowMapSpot[4]; - #ifdef _ShadowMapTransparent - uniform sampler2D shadowMapSpotTransparent[4]; - #endif + uniform sampler2DShadow shadowMapSpot[4]; + #ifdef _ShadowMapTransparent + uniform sampler2D shadowMapSpotTransparent[4]; + #endif #endif uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif @@ -93,15 +86,6 @@ uniform vec3 lightArea2; uniform vec3 lightArea3; uniform sampler2D sltcMat; uniform sampler2D sltcMag; -#ifdef _ShadowMap - #ifdef _SinglePoint - uniform sampler2DShadow shadowMapSpot[1]; - #ifdef _ShadowMapTransparent - uniform sampler2D shadowMapSpotTransparent[1]; - #endif - uniform mat4 LWVPSpotArray[1]; - #endif -#endif #endif vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol, @@ -143,37 +127,41 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV #ifdef _VoxelPass vec3 direct = vec3(dotNL); #else - #ifdef _LTC - float theta = acos(dotNV); - vec2 tuv = vec2(rough, theta / (0.5 * PI)); - tuv = tuv * LUT_SCALE + LUT_BIAS; - vec4 t = textureLod(sltcMat, tuv, 0.0); - mat3 invM = mat3( - vec3(1.0, 0.0, t.y), - vec3(0.0, t.z, 0.0), - vec3(t.w, 0.0, t.x)); - float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3); - ltcspec *= textureLod(sltcMag, tuv, 0.0).a; - float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3); - vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05; - #else + vec3 standard; #ifdef _Anisotropy - vec3 direct; if (abs(anisotropy) > 0.001 && dot(tangent, tangent) > 0.001) { vec3 bitangent = normalize(cross(n, tangent)); - direct = lambertDiffuseBRDF(albedo, dotNL) + + standard = lambertDiffuseBRDF(albedo, dotNL) + anisotropicBRDF(f0, rough, anisotropy, anisoRot, tangent, bitangent, n, l, v, dotNL, dotNV) * spec; } else { - direct = lambertDiffuseBRDF(albedo, dotNL) + + standard = lambertDiffuseBRDF(albedo, dotNL) + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; } #else - vec3 direct = lambertDiffuseBRDF(albedo, dotNL) + + standard = lambertDiffuseBRDF(albedo, dotNL) + specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec; #endif + + #ifdef _Spot + if (isSpot) { + standard *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend); + } #endif + vec3 area = vec3(0.0); + #ifdef _LTC + float theta = acos(dotNV); + vec2 tuv = vec2(rough, theta / (0.5 * PI)) * LUT_SCALE + LUT_BIAS; + vec4 t = textureLod(sltcMat, tuv, 0.0); + mat3 invM = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x)); + float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3); + ltcspec *= textureLod(sltcMag, tuv, 0.0).a; + float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3); + area = albedo * ltcdiff + ltcspec * spec * 0.05; + #endif + vec3 direct = standard + area; + // before attenuate/shadow so everything is properly shadowed in one pass #ifdef _ExtBRDF float layerWeight; @@ -195,26 +183,31 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV direct *= attenuate(dist); direct *= min(lightCol, vec3(100.0)); - #ifdef _LTC + #ifdef _LightIES + direct *= iesAttenuation(-l); + #endif + + vec3 visibility = vec3(1.0); #ifdef _ShadowMap - if (receiveShadow) { + if (receiveShadow) { + #if defined(_Spot) || defined(_LTC) #ifdef _SinglePoint - vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[0], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); + vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10.0, 1.0); + visibility = shadowTest(shadowMapSpot[0], + #ifdef _ShadowMapTransparent + shadowMapSpotTransparent[0], + #endif + lPos.xyz / lPos.w, bias + #ifdef _ShadowMapTransparent + , transparent + #endif + ); #endif #ifdef _Clusters - vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); - #ifdef _ShadowMapAtlas - tileBounds = tileBoundsSpotArray[index]; - direct *= shadowTest( + vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10.0, 1.0); + #ifdef _ShadowMapAtlas + tileBounds = tileBoundsSpotArray[index]; + visibility = shadowTest( #ifdef _ShadowMapTransparent #ifndef _SingleAtlas shadowMapAtlasSpot, shadowMapAtlasSpotTransparent @@ -233,147 +226,48 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV , transparent #endif ); - #else - if (index == 0) direct *= shadowTest(shadowMapSpot[0], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[0], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 1) direct *= shadowTest(shadowMapSpot[1], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[1], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 2) direct *= shadowTest(shadowMapSpot[2], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[2], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 3) direct *= shadowTest(shadowMapSpot[3], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[3], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - #endif - #endif - } - #endif - l_out = l; - return direct; - #endif - - #ifdef _Spot - if (isSpot) { - direct *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend); - - #ifdef _ShadowMap - if (receiveShadow) { - #ifdef _SinglePoint - vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0); - direct *= shadowTest(shadowMapSpot[0], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[0], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - #endif - #ifdef _Clusters - vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); - #ifdef _ShadowMapAtlas - tileBounds = tileBoundsSpotArray[index]; - direct *= shadowTest( + #else + if (index == 0) visibility = shadowTest(shadowMapSpot[0], #ifdef _ShadowMapTransparent - #ifndef _SingleAtlas - shadowMapAtlasSpot, shadowMapAtlasSpotTransparent - #else - shadowMapAtlas, shadowMapAtlasTransparent + shadowMapSpotTransparent[0], #endif - #else - #ifndef _SingleAtlas - shadowMapAtlasSpot - #else - shadowMapAtlas - #endif - #endif - , lPos.xyz / lPos.w, bias + lPos.xyz / lPos.w, bias + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 1) visibility = shadowTest(shadowMapSpot[1], + #ifdef _ShadowMapTransparent + shadowMapSpotTransparent[1], + #endif + lPos.xyz / lPos.w, bias + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 2) visibility = shadowTest(shadowMapSpot[2], + #ifdef _ShadowMapTransparent + shadowMapSpotTransparent[2], + #endif + lPos.xyz / lPos.w, bias + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 3) visibility = shadowTest(shadowMapSpot[3], + #ifdef _ShadowMapTransparent + shadowMapSpotTransparent[3], + #endif + lPos.xyz / lPos.w, bias #ifdef _ShadowMapTransparent , transparent #endif ); - #else - if (index == 0) direct *= shadowTest(shadowMapSpot[0], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[0], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 1) direct *= shadowTest(shadowMapSpot[1], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[1], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 2) direct *= shadowTest(shadowMapSpot[2], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[2], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 3) direct *= shadowTest(shadowMapSpot[3], - #ifdef _ShadowMapTransparent - shadowMapSpotTransparent[3], - #endif - lPos.xyz / lPos.w, bias - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - #endif #endif - } - #endif - l_out = l; - return direct; - } - #endif - - #ifdef _LightIES - direct *= iesAttenuation(-l); - #endif - - #ifdef _ShadowMap - if (receiveShadow) { + #endif + #else #ifdef _SinglePoint - #ifndef _Spot - direct *= PCFCube(shadowMapPoint[0], + visibility = PCFCube(shadowMapPoint[0], #ifdef _ShadowMapTransparent shadowMapPointTransparent[0], #endif @@ -383,10 +277,9 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV #endif ); #endif - #endif #ifdef _Clusters #ifdef _ShadowMapAtlas - direct *= PCFFakeCube( + visibility = PCFFakeCube( #ifdef _ShadowMapTransparent #ifndef _SingleAtlas shadowMapAtlasPoint, shadowMapAtlasPointTransparent @@ -406,49 +299,50 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV #endif ); #else - if (index == 0) direct *= PCFCube(shadowMapPoint[0], - #ifdef _ShadowMapTransparent - shadowMapPointTransparent[0], - #endif - ld, -l, bias, lightProj, n - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 1) direct *= PCFCube(shadowMapPoint[1], - #ifdef _ShadowMapTransparent - shadowMapPointTransparent[1], - #endif - ld, -l, bias, lightProj, n - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 2) direct *= PCFCube(shadowMapPoint[2], - #ifdef _ShadowMapTransparent - shadowMapPointTransparent[2], - #endif - ld, -l, bias, lightProj, n - #ifdef _ShadowMapTransparent - , transparent - #endif - ); - else if (index == 3) direct *= PCFCube(shadowMapPoint[3], - #ifdef _ShadowMapTransparent - shadowMapPointTransparent[3], - #endif - ld, -l, bias, lightProj, n - #ifdef _ShadowMapTransparent - , transparent - #endif - ); + if (index == 0) visibility = PCFCube(shadowMapPoint[0], + #ifdef _ShadowMapTransparent + shadowMapPointTransparent[0], + #endif + ld, -l, bias, lightProj, n + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 1) visibility = PCFCube(shadowMapPoint[1], + #ifdef _ShadowMapTransparent + shadowMapPointTransparent[1], + #endif + ld, -l, bias, lightProj, n + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 2) visibility = PCFCube(shadowMapPoint[2], + #ifdef _ShadowMapTransparent + shadowMapPointTransparent[2], + #endif + ld, -l, bias, lightProj, n + #ifdef _ShadowMapTransparent + , transparent + #endif + ); + else if (index == 3) visibility = PCFCube(shadowMapPoint[3], + #ifdef _ShadowMapTransparent + shadowMapPointTransparent[3], + #endif + ld, -l, bias, lightProj, n + #ifdef _ShadowMapTransparent + , transparent + #endif + ); #endif #endif - } + #endif + } #endif l_out = l; - return direct; + return direct * visibility; } vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol, diff --git a/leenkx/Shaders/std/mapping.glsl b/leenkx/Shaders/std/mapping.glsl index 3bc80cf6..ced2213a 100644 --- a/leenkx/Shaders/std/mapping.glsl +++ b/leenkx/Shaders/std/mapping.glsl @@ -1,41 +1,81 @@ -/* -https://github.com/JonasFolletete/glsl-triplanar-mapping +vec4 boxProjection(sampler2D image, vec3 normal, vec3 coord, float blend) { + vec3 n = normalize(normal); + vec3 N = abs(n); + vec4 color1, color2, color3; -MIT License + vec2 uv = coord.yz; + if (n.x < 0.0) { + uv.x = 1.0 - uv.x; + } + color1 = texture(image, uv); -Copyright (c) 2018 Jonas Folletête + uv = coord.xz; + if (n.y > 0.0) { + uv.x = 1.0 - uv.x; + } + color2 = texture(image, uv); -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + uv = vec2(coord.y, 1.0 - coord.x); + if (n.z > 0.0) { + uv.x = 1.0 - uv.x; + } + color3 = texture(image, uv); -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + N /= max(dot(N, vec3(1.0)), 1e-8); -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ + float limit = 0.5 + 0.5 * clamp(blend, 0.0, 1.0); + vec3 weight; + weight = N.xyz / (N.xyx + N.yzz); + weight = clamp((weight - 0.5 * (1.0 - clamp(blend, 0.0, 1.0))) / max(1e-8, clamp(blend, 0.0, 1.0)), 0.0, 1.0); -vec3 blendNormal(vec3 normal) { - vec3 blending = abs(normal); - blending = normalize(max(blending, 0.00001)); - blending /= vec3(blending.x + blending.y + blending.z); - return blending; + if (N.z < (1.0 - limit) * (N.y + N.x)) { + weight.z = 0.0; + weight.y = 1.0 - weight.x; + } + else if (N.x < (1.0 - limit) * (N.y + N.z)) { + weight.x = 0.0; + weight.z = 1.0 - weight.y; + } + else if (N.y < (1.0 - limit) * (N.x + N.z)) { + weight.y = 0.0; + weight.x = 1.0 - weight.z; + } + else { + weight = ((2.0 - limit) * N + (limit - 1.0)) / max(1e-8, clamp(blend, 0.0, 1.0)); + } + + return color1 * weight.x + color2 * weight.y + color3 * weight.z; } -vec3 triplanarMapping (sampler2D ImageTexture, vec3 normal, vec3 position) { - vec3 normalBlend = blendNormal(normal); - vec3 xColor = texture(ImageTexture, position.yz).rgb; - vec3 yColor = texture(ImageTexture, position.xz).rgb; - vec3 zColor = texture(ImageTexture, position.xy).rgb; - - return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z); +vec2 sphericalMapping(vec3 coord) { + vec3 vin = coord * 2.0 - vec3(1.0); + float len = length(vin); + float v, u; + if (len > 0.0) { + if (vin.x == 0.0 && vin.y == 0.0) { + u = 0.0; + } + else { + u = (1.0 - atan(vin.x, vin.y) / PI) * 0.5; + } + v = acos(clamp(vin.z / len, -1.0, 1.0)) / PI; + } + else { + v = u = 0.0; + } + return vec2(u, v); +} + +vec2 tubeMapping(vec3 coord) { + vec3 vin = coord * 2.0 - vec3(1.0); + float u, v; + v = - (vin.z + 1.0) * 0.5; + float len = sqrt(vin.x * vin.x + vin.y * vin.y); + if (len > 0.0) { + u = (1.0 - (atan(vin.x / len, vin.y / len) / PI)) * 0.5; + } + else { + v = u = 0.0; + } + return vec2(u, v); } diff --git a/leenkx/Shaders/std/math.glsl b/leenkx/Shaders/std/math.glsl index 3f0587f0..09845651 100644 --- a/leenkx/Shaders/std/math.glsl +++ b/leenkx/Shaders/std/math.glsl @@ -19,6 +19,14 @@ vec2 envMapEquirect(const vec3 normal) { return vec2(theta / PI2, phi / PI); } +vec2 envMapMirror(const vec3 co) { + vec3 nco = normalize(co); + nco.y -= 1.0; + float div = 2.0 * sqrt(max(-0.5 * nco.y, 0.0)); + nco /= max(1e-8, div); + return 0.5 * nco.xz + 0.5; +} + float rand(const vec2 co) { // Unreliable return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); } diff --git a/leenkx/Shaders/std/morph_target.glsl b/leenkx/Shaders/std/morph_target.glsl index 00f63884..08224ff8 100644 --- a/leenkx/Shaders/std/morph_target.glsl +++ b/leenkx/Shaders/std/morph_target.glsl @@ -4,6 +4,36 @@ uniform vec2 morphScaleOffset; uniform vec2 morphDataDim; uniform vec4 morphWeights[8]; +void getMorphedVertex(vec2 uvCoord, inout vec3 A, vec4 imorph) { + vec3 morph = texture(morphDataPos, uvCoord).rgb * morphScaleOffset.x + morphScaleOffset.y; + A += imorph.x * morph; + + morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y; + A += imorph.y * morph; + + morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y; + A += imorph.z * morph; + + morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y; + A += imorph.w * morph; +} + +void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor, vec4 imorph) { + vec3 norm = oldNor + imorph.x * (texture(morphDataNor, uvCoord).rgb * 2.0 - 1.0); + morphNor += norm; + + norm = oldNor + imorph.y * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * 2.0 - 1.0); + morphNor += norm; + + norm = oldNor + imorph.z * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * 2.0 - 1.0); + morphNor += norm; + + norm = oldNor + imorph.w * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * 2.0 - 1.0); + morphNor += norm; + + morphNor = normalize(morphNor); +} + void getMorphedVertex(vec2 uvCoord, inout vec3 A){ vec3 totalDelta = vec3(0.0); for(int i = 0; i<8; i++ ) diff --git a/leenkx/Sources/iron/App.hx b/leenkx/Sources/iron/App.hx index 796fe453..c7a393f4 100644 --- a/leenkx/Sources/iron/App.hx +++ b/leenkx/Sources/iron/App.hx @@ -237,8 +237,24 @@ class App { traitRenders.remove(f); } - public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) { - traitRenders2D.push(f); + public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) { + if (index < 0 || index >= traitRenders2D.length) { + traitRenders2D.push(f); + } else { + traitRenders2D.insert(index, f); + } + } + + public static function moveRender2D(f: kha.graphics2.Graphics->Void, newIndex: Int) { + var oldIndex = traitRenders2D.indexOf(f); + if (oldIndex != -1) { + traitRenders2D.splice(oldIndex, 1); + if (newIndex >= traitRenders2D.length) { + traitRenders2D.push(f); + } else { + traitRenders2D.insert(newIndex, f); + } + } } public static function removeRender2D(f: kha.graphics2.Graphics->Void) { diff --git a/leenkx/Sources/iron/RenderPath.hx b/leenkx/Sources/iron/RenderPath.hx index d8673579..ecb4de90 100644 --- a/leenkx/Sources/iron/RenderPath.hx +++ b/leenkx/Sources/iron/RenderPath.hx @@ -531,13 +531,11 @@ class RenderPath { if (!drawn) submitDraw(context); - #if lnx_debug // Callbacks to specific context if (contextEvents != null) { var ar = contextEvents.get(context); if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length); } - #end end(); } @@ -597,7 +595,6 @@ class RenderPath { } } - #if lnx_debug static var contextEvents: MapInt->Int->Void>> = null; public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) { if (contextEvents == null) contextEvents = new Map(); @@ -608,7 +605,13 @@ class RenderPath { } ar.push(onContext); } - #end + + public static function removeNotifyOnContext(name: String, onContext: Graphics->Int->Int->Void) { + if (contextEvents != null) { + var ar = contextEvents.get(name); + if (ar != null) ar.remove(onContext); + } + } #if rp_decals public function drawDecals(context: String) { diff --git a/leenkx/Sources/iron/Scene.hx b/leenkx/Sources/iron/Scene.hx index 7c17b671..8a88a4e7 100644 --- a/leenkx/Sources/iron/Scene.hx +++ b/leenkx/Sources/iron/Scene.hx @@ -14,6 +14,7 @@ import iron.object.SpeakerObject; import iron.object.DecalObject; import iron.object.ProbeObject; import iron.object.Tilesheet; +import iron.object.CurveObject; import iron.data.CameraData; import iron.data.MeshData; import iron.data.LightData; @@ -64,6 +65,7 @@ class Scene { #end public var empties: Array; public var animations: Array; + public var tilesheets: Array; #if lnx_skin public var armatures: Array; #end @@ -110,6 +112,7 @@ class Scene { #end empties = []; animations = []; + tilesheets = []; #if lnx_skin armatures = []; #end @@ -135,6 +138,14 @@ class Scene { // Startup scene active.addScene(format.name, null, function(sceneObject: Object) { + + if (format.properties != null) { + sceneObject.properties = new Map(); + for (p in format.properties) { + sceneObject.properties.set(p.name, cleanValue(p.value)); + } + } + // Create traits bottom-up (children first, then parents) createTraitsBottomUp(sceneObject); @@ -342,6 +353,7 @@ class Scene { if (terrainStream != null) terrainStream.update(active.camera); #end for (anim in animations) anim.update(Time.delta); + for (tilesheet in tilesheets) tilesheet.update(); for (e in empties) if (e != null && e.parent != null) e.transform.update(); } @@ -441,6 +453,11 @@ class Scene { return g; } + public function removeFromGroups(object: Object) { + if (groups == null) return; + for (name in groups.keys()) getGroup(name).remove(object); + } + public function addMeshObject(data: MeshData, materials: Vector, parent: Object = null): MeshObject { var object = new MeshObject(data, materials); parent != null ? object.setParent(parent) : object.setParent(root); @@ -450,6 +467,12 @@ class Scene { return object; } + public function addCurveObject(data: TCurveData, parent: Object = null): CurveObject { + var object = new CurveObject(data); + parent != null ? object.setParent(parent) : object.setParent(root); + return object; + } + public function addLightObject(data: LightData, parent: Object = null): LightObject { var object = new LightObject(data); parent != null ? object.setParent(parent) : object.setParent(root); @@ -709,6 +732,10 @@ class Scene { else done(ro); }); } + else if (o.type == "curve_object") { + var object = addCurveObject(Data.getCurveRawByName(format.curve_datas, o.data_ref), parent); + returnObject(object, o, done); + } else done(null); } @@ -889,11 +916,13 @@ class Scene { } else { #end // lnx_skin #if lnx_stream - streamMeshObject( + if ((o.particle_refs == null || o.particle_refs.length == 0) && o.is_particle == null && parent != null) + streamMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done); + else + returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done); #else - returnMeshObject( + returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done); #end - object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done); #if lnx_skin } #end @@ -968,20 +997,24 @@ class Scene { #end if (o.properties != null) { object.properties = new Map(); - for (p in o.properties) object.properties.set(p.name, p.value); + for (p in o.properties) { + object.properties.set(p.name, cleanValue(p.value)); + } } if (o.vertex_groups != null) { - object.vertex_groups = new Map(); - for (p in o.vertex_groups){ - var verts = []; - for(i in 0...Std.int(p.value.length/3)){ - var x = Std.parseFloat(p.value[i*3]); - var y = Std.parseFloat(p.value[i*3+1]); - var z = Std.parseFloat(p.value[i*3+2]); - verts.push(new iron.math.Vec4(x, y, z, 1)); + cast(object, MeshObject).vertexGroups = new Map(); + for (p in o.vertex_groups) { + var verts:Array = []; + + var data:kha.arrays.Float32Array = cast p.value; + + if (data != null) { + for (i in 0...Std.int(data.length / 3)) { + verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0)); + } } - object.vertex_groups.set(p.name, verts); + cast(object, MeshObject).vertexGroups.set(p.name, verts); } } @@ -1145,4 +1178,16 @@ class Scene { public function notifyOnRemove(f: Void->Void) { traitRemoves.push(f); } + + static function cleanValue(val: Dynamic): Dynamic { + if (val == null) return null; + if (untyped val.buffer != null) { + var data: kha.arrays.Float32Array = cast val; + return [for (i in 0...data.length) data[i]]; + } + if (Std.isOfType(val, Array)) { + return [for (item in (cast val: Array)) cleanValue(item)]; + } + return val; + } } diff --git a/leenkx/Sources/iron/Trait.hx b/leenkx/Sources/iron/Trait.hx index c3164943..170b67bd 100644 --- a/leenkx/Sources/iron/Trait.hx +++ b/leenkx/Sources/iron/Trait.hx @@ -125,10 +125,10 @@ class Trait { /** Add 2D render handler. **/ - public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) { + public function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) { if (_render2D == null) _render2D = []; _render2D.push(f); - App.notifyOnRender2D(f); + App.notifyOnRender2D(f, index); } /** diff --git a/leenkx/Sources/iron/data/Data.hx b/leenkx/Sources/iron/data/Data.hx index bb17a0e8..6bc0daf1 100644 --- a/leenkx/Sources/iron/data/Data.hx +++ b/leenkx/Sources/iron/data/Data.hx @@ -311,7 +311,7 @@ class Data { loadingSceneRaws.set(file, [done]); - // If no extension specified, set to .arm + // If no extension specified, set to .lnx var compressed = file.endsWith(".lz4"); var isJson = file.endsWith(".json"); var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx"; @@ -405,6 +405,13 @@ class Data { } #end + public static function getCurveRawByName(datas: Array, name: String): TCurveData { + if (datas == null || datas.length == 0) return null; + if (name == "") return datas[0]; + for (dat in datas) if (dat.name == name) return dat; + return null; + } + // Raw assets public static function getBlob(file: String, done: kha.Blob->Void) { var cached = cachedBlobs.get(file); // Is already cached diff --git a/leenkx/Sources/iron/data/Geometry.hx b/leenkx/Sources/iron/data/Geometry.hx index 9ec9c41c..e111a296 100644 --- a/leenkx/Sources/iron/data/Geometry.hx +++ b/leenkx/Sources/iron/data/Geometry.hx @@ -40,6 +40,8 @@ class Geometry { public var instancedVB: VertexBuffer = null; public var instanced = false; public var instanceCount = 0; + public var instanceElements: Array<{name: String, data: String}> = []; + public var instanceStride: Int = 0; public var positions: TVertexArray; public var normals: TVertexArray; @@ -130,11 +132,39 @@ class Geometry { structure.add("iscl", kha.graphics4.VertexData.Float3); } + if (instanceElements != null && instanceElements.length > 0) { + for (elem in instanceElements) { + if (StringTools.startsWith(elem.name, "i") && elem.name != "ipos" && elem.name != "irot" && elem.name != "iscl") { + var vdata = VertexData.Float1; + var dataStr: String = Reflect.field(elem, "data"); + switch (dataStr) { + case "float1": vdata = VertexData.Float1; + case "float2": vdata = VertexData.Float2; + case "float3": vdata = VertexData.Float3; + case "float4": vdata = VertexData.Float4; + } + structure.add(elem.name, vdata); + } + } + } + + this.instanceStride = Std.int(structure.byteSize() / 4); instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4)); instancedVB = new VertexBuffer(instanceCount, structure, usage, 1); var vertices = instancedVB.lock(); for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]); instancedVB.unlock(); + + } + + public function updateInstanced(data: Float32Array) { + if (instancedVB == null) return; + + var vertices = instancedVB.lock(); + for (i in 0...Std.int(vertices.byteLength / 4)) { + vertices.setFloat32(i * 4, data[i]); + } + instancedVB.unlock(); } public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) { diff --git a/leenkx/Sources/iron/data/SceneFormat.hx b/leenkx/Sources/iron/data/SceneFormat.hx index eaf3aeb9..669b76c5 100644 --- a/leenkx/Sources/iron/data/SceneFormat.hx +++ b/leenkx/Sources/iron/data/SceneFormat.hx @@ -40,6 +40,8 @@ typedef TSceneFormat = { @:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2 @:optional public var terrain_datas: Array; @:optional public var terrain_ref: String; + @:optional public var properties: Array; + @:optional public var curve_datas: Array; } #if js @@ -432,6 +434,7 @@ typedef TParticleData = { // Velocity public var object_align_factor: Float32Array; public var factor_random: FastFloat; + public var normal_factor: FastFloat; // Rotation public var use_rotations: Bool; public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z @@ -525,7 +528,7 @@ typedef TVertex_groups = { @:structInit class TVertex_groups { #end public var name: String; - public var value: Dynamic; + public var value: Float32Array; } #if js @@ -564,6 +567,21 @@ typedef TConstraint = { @:optional public var invert_z: Null; @:optional public var use_offset: Null; @:optional public var influence: Null; + @:optional public var use_min_x: Null; + @:optional public var use_max_x: Null; + @:optional public var use_min_y: Null; + @:optional public var use_max_y: Null; + @:optional public var use_min_z: Null; + @:optional public var use_max_z: Null; + @:optional public var use_limit_x: Null; + @:optional public var use_limit_y: Null; + @:optional public var use_limit_z: Null; + @:optional public var min_x: Null; + @:optional public var max_x: Null; + @:optional public var min_y: Null; + @:optional public var max_y: Null; + @:optional public var min_z: Null; + @:optional public var max_z: Null; } #if js @@ -622,3 +640,48 @@ typedef TTrack = { public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values @:optional public var ref_values: Array>; // ref values } + +#if js +typedef TBezierPoint = { +#else +@:structInit class TBezierPoint { +#end + public var co: Float32Array; + public var handle_left: Float32Array; + public var handle_right: Float32Array; +} + +#if js +typedef TSpline = { +#else +@:structInit class TSpline { +#end + public var closed: Bool; + public var resolution: Int; + public var points: Array; + public var material_index: Int; +} + +#if js +typedef TShapeKey = { +#else +@:structInit class TShapeKey { +#end + public var name: String; + public var value: Float; + public var points: Array; +} + +#if js +typedef TCurveData = { +#else +@:structInit class TCurveData { +#end + public var name: String; + public var object: String; + public var splines: Array; + public var strength: Float; + public var color: Float32Array; + @:optional public var material_refs: Array; + @:optional public var shape_keys: Array; +} diff --git a/leenkx/Sources/iron/data/ShaderData.hx b/leenkx/Sources/iron/data/ShaderData.hx index b099e19e..0eaa68ab 100644 --- a/leenkx/Sources/iron/data/ShaderData.hx +++ b/leenkx/Sources/iron/data/ShaderData.hx @@ -79,6 +79,8 @@ class ShaderContext { var structure: VertexStructure; var instancingType = 0; + var instanceElements: Array<{name: String, data: String}> = []; + var instanceStride: Int = 0; public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) { this.raw = raw; @@ -108,6 +110,11 @@ class ShaderContext { if (instancingType == 3 || instancingType == 4) { instStruct.add("iscl", VertexData.Float3); } + + for (e in instanceElements) + instStruct.add(e.name, parseData(e.data)); + this.instanceStride = Std.int(instStruct.byteSize() / 4); + instStruct.instanced = true; pipeState.inputLayout = [structure, instStruct]; } @@ -268,10 +275,12 @@ class ShaderContext { if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; } if (Reflect.field(elem, "name") == "irot") { irot = true; continue; } if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; } + if (Reflect.field(elem, "name").startsWith("i")) { instanceElements.push(elem); continue; } #else if (elem.name == "ipos") { ipos = true; continue; } if (elem.name == "irot") { irot = true; continue; } if (elem.name == "iscl") { iscl = true; continue; } + if (elem.name.startsWith("i")) { instanceElements.push(elem); continue; } #end structure.add(elem.name, parseData(elem.data)); } diff --git a/leenkx/Sources/iron/format/gif/Data.hx b/leenkx/Sources/iron/format/gif/Data.hx new file mode 100644 index 00000000..9326c1e4 --- /dev/null +++ b/leenkx/Sources/iron/format/gif/Data.hx @@ -0,0 +1,357 @@ +package iron.format.gif; + +import haxe.io.Bytes; + +/** + * Gif data. + */ +typedef Data = +{ + /** + * Gif version. There is only 2 Gif version exists. 87a and 89a. + * 87a have less features and does not support any extensions. + * Unknown version is adviced to be interpreted as newest (89a) official version. + */ + var version:Version; + /** + * Information about logical screen of Gif that provides basic information about Gif. + */ + var logicalScreenDescriptor:LogicalScreenDescriptor; + /** + * Global color table used for Gif. Present only if Logical Screen Descriptor contained global color table flag. + * Note that this color table not always present since frames can contain local color tables that overrides global color table. + */ + @:optional var globalColorTable:Null; + /** + * List of Gif data blocks. + */ + var blocks:List; +} + +/** + * Gif data block. Custom blocks are not supported. + */ +enum Block +{ + /** + * Gif frame block. + * Note that this block does not contain link to graphic control extension of Frame even if it is present. GraphicControl extension Block commonly present right before frame Block. + */ + BFrame(frame:Frame); + /** + * Additional extension block. This Block does not supported in 87a Gif specification version. + */ + BExtension(extension:Extension); + /** + * End of File block. Represents end of Gif data. + */ + BEOF; +} + +/** + * Extension block contains additional data about Gif image. This block does not supported by 87a version. + */ +enum Extension +{ + /** + * Graphic Control extension gives additional control over next frame, like frame delay, disposal method, alpha channel and other information. + */ + EGraphicControl(gce:GraphicControlExtension); + /** + * Commentary extension. Not show up as any visual, just a text in file. + */ + EComment(text:String); + /** + * Text extension. Must work as text rendering on the image, but ignored by all major Gif decoders. + */ + EText(pte:PlainTextExtension); + /** + * Application extension allow to insert additional application data into Gif. Mostly used app extension is NETSCAPE2.0 looping extension, used to set up amount of loops in frame. + */ + EApplicationExtension(ext:ApplicationExtension); + + /** + * Unknown extension. + */ + EUnknown(id:Int, data:Bytes); +} + +/** + * Application extension. Mostly used only for one reason - setting up loops count. There is exist other app extensions but they are really rare. + */ +enum ApplicationExtension +{ + /** + * NETSCAPE2.0 looping extension. Contains only amount of animation repeats. + * Note that there is two NETSCAPE2.0 app extensions for Gif format and the type of extension is stored in first byte of data. Looping extension have ID 1. + */ + AENetscapeLooping(loops:Int); + /** + * Unknown or unsupported app extension. + */ + AEUnknown(name:String, version:String, data:Bytes); +} + +/** + * Typical color table for Gif image. + * Can contain 2, 4, 8, 16, 32, 64, 128 or 256 colors. + * Data stored in RGB format. Information about alpha channel provided by Graohic Control Extension. + */ +typedef ColorTable = Bytes; + +/** + * Single frame of the image. + * Actually it's a merge of 3 consequent blocks: + * 1. Image Descriptor. + * Contains frame informations like position, size, existing of local color table and interlaced flag. + * 2. [Local color table]. + * Only present if Image Descriptor contains local color table flag. Overrides global color table. + * 3. Pixel data blocks. + * LZW compressed pixel data. + */ +typedef Frame = +{ + /** + * X position of image on the Logical Screen + */ + var x:Int; + + /** + * Y position of image on the Logical Screen + */ + var y:Int; + + /** + * Width of image in pixels + */ + var width:Int; + + /** + * Height of image in pixels + */ + var height:Int; + + /** + * Is this image uses local color table? + */ + var localColorTable:Bool; + + /** + * Is this image written in interlace mode? + * Note: The pixel data already deinterlaced and this flag presented only for information purpose (and for Writer when there is one). + */ + var interlaced:Bool; + + /** + * Is local color table sorted in order of decreasing priority? + */ + var sorted:Bool; + + /** + * Size of local color table + */ + var localColorTableSize:Int; + + /** + * Pixel data of frame. Stored as Indexed colors, 1 byte per pixel. + */ + var pixels:Bytes; + + /** + * Local color table used by frame. Stored as 3-byte RGB colors. If value is null, must be used global color table. + */ + var colorTable:ColorTable; +} + +/** + * Graphic Control Extension block, used for setting up disposal method, transparency, delay and user input. + */ +typedef GraphicControlExtension = +{ + /** + * Disposal method of frame. + */ + var disposalMethod:DisposalMethod; + /** + * Is image must wait for user input, before dispose? + * This flag may be used by user-defined program but absolutely ignored by any Gif players. + */ + var userInput:Bool; + /** + * Is image have transparency? + */ + var hasTransparentColor:Bool; + /** + * Delay, before next image appears. Delay is in centiseconds (1 centisecond = 1/100 seconds). + * Note: Some players (like FastStone) cut fraction of elapsed time when progressing to next frame which results in small timing error. + * Recommended to use `time -= delay` instead of `time = 0`. + */ + var delay:Int; + /** + * Index in color table that used as transparent. + */ + var transparentIndex:Int; +} + +/** + * Extension for rendering text on Gif logical screen. It does not supported by major Gif decoders. + * Font and text size decision is left to decoder. (recommended to decide based on grid/cell size) + * Text must be rendered with one character at cell. + * It's recommended to replace any characters less than 0x20 and greater than 0xf7 to be rendered as Space (0x20) + */ +typedef PlainTextExtension = +{ + /** + * X position of text grid on Logical Screen. + */ + var textGridX:Int; + /** + * Y position of text grid on Logical Screen. + */ + var textGridY:Int; + /** + * Width of text grid in pixels. + */ + var textGridWidth:Int; + /** + * Height of text grid in pixels. + */ + var textGridHeight:Int; + /** + * Width of character cell in text grid. + */ + var charCellWidth:Int; + /** + * Height of character cell in text grid. + */ + var charCellHeight:Int; + /** + * Foreground/character color index. + */ + var textForegroundColorIndex:Int; + /** + * Background color index. + */ + var textBackgroundColorIndex:Int; + /** + * Text to render. + */ + var text:String; +} + +/** + * Logical screen descriptor of GIF file. + * Contains very basic information about Gif. + */ +typedef LogicalScreenDescriptor = +{ + /** + * Width of GIF image in pixels + */ + var width:Int; + + /** + * Height of GIF image in pixels + */ + var height:Int; + + /** + * Is this file uses global color table? + */ + var hasGlobalColorTable:Bool; + + /** + * Specification: + * Number of bits per primary color available + to the original image, minus 1. This value represents the size of + the entire palette from which the colors in the graphic were + selected, not the number of colors actually used in the graphic. + For example, if the value in this field is 3, then the palette of + the original image had 4 bits per primary color available to create + the image. This value should be set to indicate the richness of + the original palette, even if not every color from the whole + palette is available on the source machine. + */ + var colorResolution:Int; + + /** + * Specification: + * Indicates whether the Global Color Table is sorted. + If the flag is set, the Global Color Table is sorted, in order of + decreasing importance. Typically, the order would be decreasing + frequency, with most frequent color first. This assists a decoder, + with fewer available colors, in choosing the best subset of colors; + the decoder may use an initial segment of the table to render the + graphic. + */ + var sorted:Bool; + + /** + * Size of global color table. + */ + var globalColorTableSize:Int; + + /** + * Background color index in global color table + */ + var backgroundColorIndex:Int; + + /** + * Factor used to compute an approximation of the aspect ratio of the pixel in the original image. + */ + var pixelAspectRatio:Float; +} + +/** + * Version of Gif file. + * The only 2 official versions is GIF87a and GIF89a. + */ +enum Version +{ + /** + * First version of Gif file format from May 1987. + * + * Note: The checking of unsupported blocks disabled by default to save some time. To enable supported blocks check set `yagp_strict_version_check` debug variable. + */ + GIF87a; + /** + * Second and actual version of Gif file format from July 1989. + */ + GIF89a; + /** + * Unknown version of Gif file. + */ + Unknown(version:String); +} + +/** + * Disposal method of GIF frame. + */ +enum DisposalMethod +{ + /** + * The disposal method is unspecified. Action on demand of viewer. + * + * Mostly interpreted as NO_ACTION. + */ + UNSPECIFIED; + /** + * No action required. + */ + NO_ACTION; + /** + * Fill frame rectangle with background color. + * + * Usage note: + * Most renderers clears to transparency instead of filling background color, when frame's transparent color index not equals to background color index. + */ + FILL_BACKGROUND; + /** + * Render previous state of gif as it before rendering disposing frame. + */ + RENDER_PREVIOUS; + /** + * Reserved disposal methods. + */ + UNDEFINED(index:Int); +} diff --git a/leenkx/Sources/iron/format/gif/GifEncoder.hx b/leenkx/Sources/iron/format/gif/GifEncoder.hx new file mode 100644 index 00000000..c121e65e --- /dev/null +++ b/leenkx/Sources/iron/format/gif/GifEncoder.hx @@ -0,0 +1,359 @@ +package iron.format.gif; + +/* + * No copyright asserted on the source code of this class. May be used + * for any purpose. + * + * Original code by Kevin Weiner, FM Software. + * Adapted by Thomas Hourdel (https://github.com/Chman/Moments) + * Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m + */ + +import haxe.io.UInt8Array; +import haxe.io.BytesOutput; + +@:enum abstract GifRepeat(Int) + from Int to Int { + var None = 0; + var Infinite = -1; +} + +@:enum abstract GifQuality(Int) + from Int to Int { + var Best = 1; + var VeryHigh = 10; + var QuiteHigh = 20; + var High = 35; + var Mid = 50; + var Low = 65; + var QuiteLow = 80; + var VeryLow = 90; + var Worst = 100; +} + +class GifEncoder { + + var width: Int; + var height: Int; + var framerate: Float = 24; // used if frame.delay < 0 + var repeat: Int = -1; // -1: infinite, 0: none, >0: repeat count + + var colorDepth: Int = 8; // Number of bit planes + var paletteSize: Int = 7; // Color table size (bits-1) + var sampleInterval: Int = 10; // Default sample interval for quantizer + + //caches + var pixels: UInt8Array; + var indexedPixels: UInt8Array; // Converted frame indexed to palette + var colorTab: UInt8Array; // RGB palette + var usedEntry: Array; // Active palette entries + // + var nq: NeuQuant; + var lzwEncoder: LzwEncoder; + //internal + var started: Bool = false; + var first_frame: Bool = true; + + //:todo: error handling could be better - but throw inside of another thread on cpp is too quiet + + /** Allows a custom print handler for error messages. + Defaults to Sys.println on sys targets, and trace otherwise. */ + public var print: Dynamic->Void; + +// Public API + + /** Construct a gif encoder with options: + + frame width/height: + Default is 0, required + + framerate: + This is used if an added frame has a delay that is negative. + + repeat: + Default is 0 (no repeat); -1 means play indefinitely. + Use GifRepeat for clarity + + quality: + Sets quality of color quantization (conversion of images to + the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1) + produce better colors, but slow processing significantly. Higher values will speed + up the quantization pass at the cost of lower image quality (maximum = 100). */ + public function new( + _frame_width:Int, + _frame_height:Int, + _framerate:Float, + _repeat:Int = GifRepeat.Infinite, + _quality:Int = 10 + ) { + + #if sys + print = Sys.println; + #else + print = function(v) { trace(v); } + #end + + width = _frame_width; + height = _frame_height; + framerate = _framerate; + repeat = _repeat; + + sampleInterval = Std.int(clamp(_quality, 1, 100)); + usedEntry = [for (i in 0...256) false]; + + pixels = new UInt8Array(width * height * 3); + indexedPixels = new UInt8Array(width * height); + + nq = new NeuQuant(); + lzwEncoder = new LzwEncoder(); + + } //new + + public function start(output:BytesOutput) : Void { + + if(output == null) { + print("gif: start() output must not be null."); + return; + } + + output.writeString("GIF89a"); + + write_LSD(output); + + started = true; + + } //start + + public function add(output:BytesOutput, frame:GifFrame) : Void { + + if(output == null) { + print("gif: add() output must not be null."); + return; + } + + if(!started) { + print("gif: add() requires start to be called before adding frames."); + return; + } + + var pixels = get_pixels(frame); + analyze(pixels); + + if(first_frame) { + + write_palette(output); + + if(repeat != GifRepeat.None) { + write_NetscapeExt(output); + } + + first_frame = false; + + } //first_frame + + var delay = if(frame.delay < 0) { + 1.0/framerate; + } else { + frame.delay; + } + + write_GraphicControlExt(output, delay); + write_image_desc(output, first_frame); + + if(!first_frame) { + write_palette(output); + } + + write_pixels(output); + + } //add + + public function commit(output:BytesOutput) : Void { + + if(output == null) { + print("gif: commit() output must be not null."); + return; + } + + if(!started) { + print("gif: commit() called without start() being called first."); + return; + } + + output.writeByte(0x3b); // Gif trailer + output.flush(); + output.close(); + + started = false; + first_frame = true; + + } //commit + + //helpers + + function get_pixels(frame:GifFrame):UInt8Array { + + //if not flipped we can use the data as is + if (!frame.flippedY) return frame.data; + + //otherwise flip it, and return the cached array + var stride = width * 3; + for(y in 0...height) { + var begin = (height - 1 - y) * stride; + pixels.view.buffer.blit(y * stride, frame.data.view.buffer, begin, stride); + } + + return pixels; + + } //get_pixels + + function analyze(pixels:UInt8Array) { + + // Create reduced palette + nq.reset(pixels, pixels.length, sampleInterval); + colorTab = nq.process(); + + // Map image pixels to new palette + var k:Int = 0; + for (i in 0...(width * height)) { + var r = pixels[k++] & 0xff; + var g = pixels[k++] & 0xff; + var b = pixels[k++] & 0xff; + var index = nq.map(r, g,b); + usedEntry[index] = true; + indexedPixels[i] = index; + } + + } //analyze + + //writers + // + + /** Writes Logical Screen Descriptor. */ + function write_LSD(output:BytesOutput) { + // + + // Logical screen size + output.writeInt16(width); + output.writeInt16(height); + + // Packed fields + output.writeByte(0x80 | // 1 : global color table flag = 1 (gct used) + 0x70 | // 2-4 : color resolution = 7 + 0x00 | // 5 : gct sort flag = 0 + paletteSize); // 6-8 : gct size + + output.writeByte(0); // Background color index + output.writeByte(0); // Pixel aspect ratio - assume 1:1 + + } //write_LSD + + /** Writes Netscape application extension to define repeat count. */ + function write_NetscapeExt(output:BytesOutput):Void { + + var repeats = repeat; + if(repeats == GifRepeat.Infinite || repeats < 0) repeats = 0; + if(repeats == GifRepeat.None) repeats = -1; + + output.writeByte(0x21); // Extension introducer + output.writeByte(0xff); // App extension label + output.writeByte(11); // Block size + output.writeString("NETSCAPE" + "2.0"); // App id + auth code + output.writeByte(3); // Sub-block size + output.writeByte(1); // Loop sub-block id + output.writeInt16(repeats); // Loop count (extra iterations, 0=repeat forever) + output.writeByte(0); // Block terminator + + } //write_NetscapeExt + + /** Write color table. */ + function write_palette(output:BytesOutput):Void { + + output.write(colorTab.view.buffer); + + var n:Int = (3 * 256) - colorTab.length; + + for (i in 0...n) { + output.writeByte(0); + } + + } //write_palette + + /** Encodes and writes pixel data. */ + function write_pixels(output:BytesOutput):Void { + + lzwEncoder.reset(indexedPixels, colorDepth); + lzwEncoder.encode(output); + + } //write_pixels + + /** Writes Image Descriptor. */ + function write_image_desc(output:BytesOutput, first:Bool):Void { + + output.writeByte(0x2c); // Image separator + output.writeInt16(0); // Image position x = 0 + output.writeInt16(0); // Image position y = 0 + output.writeInt16(width); // Image width + output.writeInt16(height); // Image height + + //Write LCT, or GCT + + if(first) { + + output.writeByte(0); // No LCT - GCT is used for first (or only) frame + + } else { + + output.writeByte(0x80 | // 1 local color table 1=yes + 0 | // 2 interlace - 0=no + 0 | // 3 sorted - 0=no + 0 | // 4-5 reserved + paletteSize); // 6-8 size of color table + + } //else + + } //write_image_desc + + /** Writes Graphic Control Extension. Delay is in seconds, floored and converted to 1/100 of a second */ + function write_GraphicControlExt(output:BytesOutput, delay:Float):Void { + + output.writeByte(0x21); // Extension introducer + output.writeByte(0xf9); // GCE label + output.writeByte(4); // data block size + + // Packed fields + output.writeByte(0 | // 1:3 reserved + 0 | // 4:6 disposal + 0 | // 7 user input - 0 = none + 0 ); // 8 transparency flag + + //convert to 1/100 sec + var delay_val = Math.floor(delay * 100); + + output.writeInt16(delay_val); // Delay x 1/100 sec + output.writeByte(0); // Transparent color index + output.writeByte(0); // Block terminator + + } //write_GraphicControlExt + + /** Clamp a value between a and b and return the clamped version */ + static inline public function clamp(value:Float, a:Float, b:Float):Float + { + return ( value < a ) ? a : ( ( value > b ) ? b : value ); + } + +} //GifEncoder + + +typedef GifFrame = { + + /** Delay of the frame in seconds. This value gets floored + when encoded due to gif format requirements. If this value is negative, + the default encoder frame rate will be used. */ + var delay: Float; + /** Whether or not this frame should be flipped on the Y axis */ + var flippedY: Bool; + /** Pixels data in unsigned bytes, rgb format */ + var data: UInt8Array; + +} diff --git a/leenkx/Sources/iron/format/gif/LzwEncoder.hx b/leenkx/Sources/iron/format/gif/LzwEncoder.hx new file mode 100644 index 00000000..19d932ff --- /dev/null +++ b/leenkx/Sources/iron/format/gif/LzwEncoder.hx @@ -0,0 +1,350 @@ + package iron.format.gif; + +/* + * No copyright asserted on the source code of this class. May be used + * for any purpose, however, refer to the Unisys LZW patent for restrictions + * on use of the associated LZWEncoder class : + * + * The Unisys patent expired on 20 June 2003 in the USA, in Europe it expired + * on 18 June 2004, in Japan the patent expired on 20 June 2004 and in Canada + * it expired on 7 July 2004. The U.S. IBM patent expired 11 August 2006, The + * Software Freedom Law Center says that after 1 October 2006, there will be + * no significant patent claims interfering with employment of the GIF format. + * + * Original code by Kevin Weiner, FM Software. + * Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. + * Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m + * + */ + +import haxe.io.Int32Array; +import haxe.io.UInt8Array; + +class LzwEncoder { + static var EOF(default, never):Int = -1; + + var pixAry:UInt8Array; + var initCodeSize:Int; + var curPixel:Int; + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley (mgardi@watdcsu.waterloo.edu) + + // General DEFINEs + + static var BITS(default, never):Int = 12; + + static var HSIZE(default, never):Int = 5003; // 80% occupancy + + // GIF Image compression - modified 'compress' + // + // Based on: compress.c - File compression ala IEEE Computer, June 1984. + // + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + // Jim McKie (decvax!mcvax!jim) + // Steve Davies (decvax!vax135!petsd!peora!srd) + // Ken Turkowski (decvax!decwrl!turtlevax!ken) + // James A. Woods (decvax!ihnp4!ames!jaw) + // Joe Orost (decvax!vax135!petsd!joe) + + var n_bits:Int; // number of bits/code + var maxbits:Int = BITS; // user settable max # bits/code + var maxcode:Int; // maximum code, given n_bits + var maxmaxcode:Int = 1 << BITS; // should NEVER generate this code + + var htab:Int32Array; + var codetab:Int32Array; + + var hsize:Int = HSIZE; // for dynamic table sizing + + var free_ent:Int = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + var clear_flg:Bool = false; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + var g_init_bits:Int; + + var ClearCode:Int; + var EOFCode:Int; + + // output + // + // output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // outputs: + // outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + var cur_accum:Int = 0; + var cur_bits:Int = 0; + + var masks:Array = + [ + 0x0000, + 0x0001, + 0x0003, + 0x0007, + 0x000F, + 0x001F, + 0x003F, + 0x007F, + 0x00FF, + 0x01FF, + 0x03FF, + 0x07FF, + 0x0FFF, + 0x1FFF, + 0x3FFF, + 0x7FFF, + 0xFFFF ]; + + // Number of characters so far in this 'packet' + var a_count:Int; + + // Define the storage for the packet accumulator + var accum:UInt8Array; + + //---------------------------------------------------------------------------- + public function new() + { + htab = new Int32Array(HSIZE); + codetab = new Int32Array(HSIZE); + accum = new UInt8Array(256); + } + + //Reset the encoder to new pixel data and default values + public function reset(pixels:UInt8Array, color_depth:Int) { //width and height used to be passed in though they were never used + pixAry = pixels; + initCodeSize = Std.int(Math.max(2, color_depth)); + + maxbits = BITS; + maxmaxcode = 1 << BITS; + hsize = HSIZE; + free_ent = 0; + clear_flg = false; + cur_accum = 0; + cur_bits = 0; + } + + // add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + function add(c:UInt, out:haxe.io.Output):Void + { + accum[a_count++] = c; + if (a_count >= 254) + flush(out); + } + + // Clear out the hash table + + // table clear for block compress + function clearTable(out:haxe.io.Output):Void + { + resetCodeTable(hsize); + free_ent = ClearCode + 2; + clear_flg = true; + + output(ClearCode, out); + } + + // reset code table + function resetCodeTable(hsize:Int):Void + { + for (i in 0...hsize) + htab[i] = -1; + } + + function compress(init_bits:Int, out:haxe.io.Output):Void + { + var fcode:Int; + var i:Int /* = 0 */; + var c:Int; + var ent:Int; + var disp:Int; + var hsize_reg:Int; + var hshift:Int; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = maxCode(n_bits); + + ClearCode = 1 << (init_bits - 1); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + a_count = 0; // clear packet + + ent = nextPixel(); + + hshift = 0; + fcode = hsize; + while (fcode < 65536) { + ++hshift; + fcode *= 2; + } + + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + resetCodeTable(hsize_reg); // clear hash table + + output(ClearCode, out); + + while ((c = nextPixel()) != EOF) + { + fcode = (c << maxbits) + ent; + i = (c << hshift) ^ ent; // xor hashing + + if (htab[i] == fcode) + { + ent = codetab[i]; + continue; + } + else if (htab[i] >= 0) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if (i == 0) + disp = 1; + do + { + if ((i -= disp) < 0) + i += hsize_reg; + + if (htab[i] == fcode) + { + ent = codetab[i]; + break; + } + } while (htab[i] >= 0); + if (htab[i] == fcode) continue; + } + output(ent, out); + ent = c; + if (free_ent < maxmaxcode) + { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } + else + clearTable(out); + } + // Put out the final code. + output(ent, out); + output(EOFCode, out); + } + + //---------------------------------------------------------------------------- + public function encode(os:haxe.io.Output):Void + { + os.writeByte( initCodeSize ); // write "initial code size" byte + curPixel = 0; + compress(initCodeSize + 1, os); // compress and write the pixel data + os.writeByte(0); // write block terminator + } + + // flush the packet to disk, and reset the accumulator + function flush(out:haxe.io.Output):Void + { + if (a_count > 0) + { + out.writeByte(a_count); + out.writeBytes(accum.view.buffer, 0, a_count); + a_count = 0; + } + } + + inline function maxCode(n_bits:Int):Int + { + return (1 << n_bits) - 1; + } + + //---------------------------------------------------------------------------- + // Return the next pixel from the image + //---------------------------------------------------------------------------- + function nextPixel():Int + { + if (curPixel == pixAry.length) + return EOF; + + curPixel++; + return pixAry[curPixel - 1] & 0xff; + } + + function output(code:Int, out:haxe.io.Output):Void + { + cur_accum &= masks[cur_bits]; + + if (cur_bits > 0) + cur_accum |= (code << cur_bits); + else + cur_accum = code; + + cur_bits += n_bits; + + while (cur_bits >= 8) + { + add(cur_accum & 0xff, out); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (free_ent > maxcode || clear_flg) + { + if (clear_flg) + { + maxcode = maxCode(n_bits = g_init_bits); + clear_flg = false; + } + else + { + ++n_bits; + if (n_bits == maxbits) + maxcode = maxmaxcode; + else + maxcode = maxCode(n_bits); + } + } + + if (code == EOFCode) + { + // At EOF, write the rest of the buffer. + while (cur_bits > 0) + { + add(cur_accum & 0xff, out); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush(out); + } + } + +} diff --git a/leenkx/Sources/iron/format/gif/NeuQuant.hx b/leenkx/Sources/iron/format/gif/NeuQuant.hx new file mode 100644 index 00000000..271106d5 --- /dev/null +++ b/leenkx/Sources/iron/format/gif/NeuQuant.hx @@ -0,0 +1,541 @@ +package iron.format.gif; + +/* + * Copyright (c) 1994 Anthony Dekker + * Ported to Java by Kevin Weiner, FM Software + * Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m + * + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. + * See "Kohonen neural networks for optimal colour quantization" + * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. + * for a discussion of the algorithm. + * + * Any party obtaining a copy of these files from the author, directly or + * indirectly, is granted, free of charge, a full and unrestricted irrevocable, + * world-wide, paid up, royalty-free, nonexclusive right and license to deal + * in this software and documentation files (the "Software"), including without + * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons who receive + * copies from any such party to do so, with the only requirement being + * that this copyright notice remain intact. + * + */ + +import haxe.io.Int32Array; +import haxe.io.UInt8Array; + +class NeuQuant { + + inline static var netsize : Int = 256; // Number of colours used + + // Four primes near 500 - assume no image has a length so large that it is divisible by all four primes + inline static var prime1 : Int = 499; + inline static var prime2 : Int = 491; + inline static var prime3 : Int = 487; + inline static var prime4 : Int = 503; + + inline static var minpicturebytes : Int = (3 * prime4); // Minimum size for input image + + // Network Definitions + inline static var netbiasshift : Int = 4; // Bias for colour values + inline static var ncycles : Int = 100; // No. of learning cycles + + // Defs for freq and bias + inline static var intbiasshift : Int = 16; // Bias for fractions + inline static var intbias : Int = (1 << intbiasshift); + inline static var gammashift : Int = 10; // Gamma = 1024 + inline static var gamma : Int = (1 << gammashift); + inline static var betashift : Int = 10; + inline static var beta : Int = (intbias >> betashift); // Beta = 1/1024 + inline static var betagamma : Int = (intbias << (gammashift - betashift)); + + // Defs for decreasing radius factor + inline static var initrad : Int = (netsize >> 3); // For 256 cols, radius starts + inline static var radiusbiasshift : Int = 6; // At 32.0 biased by 6 bits + inline static var radiusbias : Int = (1 << radiusbiasshift); + inline static var initradius : Int = (initrad * radiusbias); // And decreases by a + inline static var radiusdec : Int = 30; // Factor of 1/30 each cycle + + // Defs for decreasing alpha factor + inline static var alphabiasshift : Int = 10; /* alpha starts at 1.0 */ + inline static var initalpha : Int = (1 << alphabiasshift); + + // Radbias and alpharadbias used for radpower calculation + inline static var radbiasshift : Int = 8; + inline static var radbias : Int = (1 << radbiasshift); + inline static var alpharadbshift : Int = (alphabiasshift + radbiasshift); + inline static var alpharadbias : Int = (1 << alpharadbshift); + + var alphadec:Int; // Biased by 10 bits + + // Types and Global Variables + + var thepicture: UInt8Array; // The input image itself + var lengthcount: Int; // Lengthcount = H*W*3 + var samplefac: Int; // Sampling factor 1..30 + var network: Int32Array; // The network itself - [netsize][4] + var netindex: Int32Array; // For network lookup - really 256 + var bias: Int32Array; // Bias array for learning + var freq: Int32Array; // Frequency array for learning + var radpower: Int32Array; // Radpower for precomputation + var colormap_map: UInt8Array; // Cached color map array + var colormap_index: Int32Array; // Cached color map index + + public function new() + { + netindex = new Int32Array(256); + bias = new Int32Array(netsize); + freq = new Int32Array(netsize); + radpower = new Int32Array(initrad); + network = new Int32Array(netsize * 4); + colormap_map = new UInt8Array(3 * netsize); + colormap_index = new Int32Array(netsize); + } + + // Reset network in range (0,0,0) to (255,255,255) and set parameters + public function reset(thepic:UInt8Array, len:Int, sample:Int):Void { + thepicture = thepic; + lengthcount = len; + samplefac = sample; + + for (i in 0...netsize) { + network[i*4 + 0] = network[i*4 + 1] = network[i*4 + 2] = Std.int((i << (netbiasshift + 8)) / netsize); + freq[i] = Std.int(intbias / netsize); // 1 / netsize + bias[i] = 0; // allocated to zero? + } + } + + public function colormap():UInt8Array + { + for(i in 0...netsize) { + colormap_index[network[i * 4 + 3]] = i; + } + + var k:Int = 0; + for (i in 0...netsize) + { + var j = colormap_index[i]; + colormap_map[k++] = network[j * 4]; + colormap_map[k++] = network[j * 4 + 1]; + colormap_map[k++] = network[j * 4 + 2]; + } + + return colormap_map; + } + + // Insertion sort of network and building of netindex[0..255] (to do after unbias) + public function inxbuild():Void + { + var i:Int; + var j:Int; + var smallpos:Int; + var smallval:Int; + var previouscol:Int; + var startpos:Int; + + previouscol = 0; + startpos = 0; + + for (i in 0...netsize) + { + smallpos = i; + smallval = network[i*4 + 1]; // Index on g + + // Find smallest in i..netsize-1 + for (j in (i + 1)...netsize) + { + if (network[j*4 + 1] < smallval) + { + smallpos = j; + smallval = network[j*4 + 1]; // Index on g + } + } + + + // Swap p (i) and q (smallpos) entries + if (i != smallpos) + { + j = network[smallpos*4 + 0]; + network[smallpos*4 + 0] = network[i*4 + 0]; + network[i*4 + 0] = j; + j = network[smallpos*4 + 1]; + network[smallpos*4 + 1] = network[i*4 + 1]; + network[i*4 + 1] = j; + j = network[smallpos*4 + 2]; + network[smallpos*4 + 2] = network[i*4 + 2]; + network[i*4 + 2] = j; + j = network[smallpos*4 + 3]; + network[smallpos*4 + 3] = network[i*4 + 3]; + network[i*4 + 3] = j; + } + + // Smallval entry is now in position i + if (smallval != previouscol) + { + netindex[previouscol] = (startpos + i) >> 1; + + for (j in (previouscol + 1)...smallval) + netindex[j] = i; + + previouscol = smallval; + startpos = i; + } + } + + var maxnetpos = netsize - 1; + + netindex[previouscol] = (startpos + maxnetpos) >> 1; + + for (j in (previouscol + 1)...256) + netindex[j] = maxnetpos; + } + + // Main learning Loop + public function learn():Void + { + var i:Int; + var j:Int; + var b:Int; + var g:Int; + var r:Int; + var radius:Int; + var rad:Int; + var alpha:Int; + var step:Int; + var delta:Int; + var samplepixels:Int; + + var p:UInt8Array; + var pix:Int; + var lim:Int; + + if (lengthcount < minpicturebytes) + samplefac = 1; + + alphadec = 30 + Std.int((samplefac - 1) / 3); + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = Std.int(lengthcount / (3 * samplefac)); + delta = Std.int(samplepixels / ncycles); + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + + if (rad <= 1) + rad = 0; + + for (i in 0...rad) + radpower[i] = Std.int(alpha * (((rad * rad - i * i) * radbias) / (rad * rad))); + + if (lengthcount < minpicturebytes) + { + step = 3; + } + else if ((lengthcount % prime1) != 0) + { + step = 3 * prime1; + } + else + { + if ((lengthcount % prime2) != 0) + { + step = 3 * prime2; + } + else + { + if ((lengthcount % prime3) != 0) + step = 3 * prime3; + else + step = 3 * prime4; + } + } + + i = 0; + while (i < samplepixels) + { + b = (p[pix + 0] & 0xff) << netbiasshift; + g = (p[pix + 1] & 0xff) << netbiasshift; + r = (p[pix + 2] & 0xff) << netbiasshift; + j = contest(b, g, r); + + altersingle(alpha, j, b, g, r); + + if (rad != 0) + alterneigh(rad, j, b, g, r); // Alter neighbours + + pix += step; + + if (pix >= lim) + pix -= lengthcount; + + i++; + + if (delta == 0) + delta = 1; + + if (i % delta == 0) + { + alpha -= Std.int(alpha / alphadec); + radius -= Std.int(radius / radiusdec); + rad = radius >> radiusbiasshift; + + if (rad <= 1) + rad = 0; + + for (j in 0...rad) + radpower[j] = Std.int(alpha * (((rad * rad - j * j) * radbias) / (rad * rad))); + } + } + } + + // Search for BGR values 0..255 (after net is unbiased) and return colour index + public function map(b:Int, g:Int, r:Int):Int + { + var i:Int; + var j:Int; + var dist:Int; + var a:Int; + var bestd:Int; + var best:Int; + + bestd = 1000; // Biggest possible dist is 256*3 + best = -1; + i = netindex[g]; // Index on g + j = i - 1; // Start at netindex[g] and work outwards + + while ((i < netsize) || (j >= 0)) + { + if (i < netsize) + { + dist = network[i*4 + 1] - g; // Inx key + + if (dist >= bestd) + { + i = netsize; // Stop iter + } + else + { + if (dist < 0) + dist = -dist; + + a = network[i*4 + 0] - b; + + if (a < 0) + a = -a; + + dist += a; + + if (dist < bestd) + { + a = network[i*4 + 2] - r; + + if (a < 0) + a = -a; + + dist += a; + + if (dist < bestd) + { + bestd = dist; + best = network[i*4 + 3]; + } + } + + i++; + } + } + + if (j >= 0) + { + dist = g - network[j*4 + 1]; // Inx key - reverse dif + + if (dist >= bestd) + { + j = -1; // Stop iter + } + else + { + if (dist < 0) + dist = -dist; + + a = network[j*4 + 0] - b; + + if (a < 0) + a = -a; + + dist += a; + + if (dist < bestd) + { + a = network[j*4 + 2] - r; + + if (a < 0) + a = -a; + + dist += a; + + if (dist < bestd) + { + bestd = dist; + best = network[j*4 + 3]; + } + } + + j--; + } + } + } + + return best; + } + + public function process():UInt8Array + { + learn(); + unbiasnet(); + inxbuild(); + return colormap(); + } + + // Unbias network to give byte values 0..255 and record position i to prepare for sort + public function unbiasnet():Void + { + for (i in 0...netsize) + { + network[i*4] >>= netbiasshift; + network[i*4 + 1] >>= netbiasshift; + network[i*4 + 2] >>= netbiasshift; + network[i*4 + 3] = i; // Record colour no + } + } + + // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] + function alterneigh(rad:Int, i:Int, b:Int, g:Int, r:Int):Void + { + var j:Int; + var k:Int; + var lo:Int; + var hi:Int; + var a:Int; + var m:Int; + + lo = i - rad; + + if (lo < -1) + lo = -1; + + hi = i + rad; + + if (hi > netsize) + hi = netsize; + + j = i + 1; + k = i - 1; + m = 1; + + while ((j < hi) || (k > lo)) + { + a = radpower[m++]; + + if (j < hi) + { + network[j * 4 + 0] -= Std.int((a * (network[j * 4 + 0] - b)) / alpharadbias); + network[j * 4 + 1] -= Std.int((a * (network[j * 4 + 1] - g)) / alpharadbias); + network[j * 4 + 2] -= Std.int((a * (network[j * 4 + 2] - r)) / alpharadbias); + j++; + } + + if (k > lo) + { + network[k * 4 + 0] -= Std.int((a * (network[k * 4 + 0] - b)) / alpharadbias); + network[k * 4 + 1] -= Std.int((a * (network[k * 4 + 1] - g)) / alpharadbias); + network[k * 4 + 2] -= Std.int((a * (network[k * 4 + 2] - r)) / alpharadbias); + k--; + } + } + } + + // Move neuron i towards biased (b,g,r) by factor alpha + function altersingle(alpha:Int, i:Int, b:Int, g:Int, r:Int):Void + { + /* Alter hit neuron */ + network[i*4 + 0] -= Std.int((alpha * (network[i*4 + 0] - b)) / initalpha); + network[i*4 + 1] -= Std.int((alpha * (network[i*4 + 1] - g)) / initalpha); + network[i*4 + 2] -= Std.int((alpha * (network[i*4 + 2] - r)) / initalpha); + } + + inline function make_abs(value:Int) : Int { + var tmp = value >> 31; + value ^= tmp; + value += tmp & 1; + return value; + } + + // Search for biased BGR values + static inline var bestd_init = ~(1 << 31); + function contest(b:Int, g:Int, r:Int):Int + { + // Finds closest neuron (min dist) and updates freq + // Finds best neuron (min dist-bias) and returns position + // For frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma*((1/netsize)-freq[i]) + + var i:Int; + var dist:Int; + var a:Int; + var biasdist:Int; + var betafreq:Int; + var bestpos:Int; + var bestbiaspos:Int; + var bestd:Int; + var bestbiasd:Int; + + bestd = bestd_init; + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + + for (i in 0...netsize) + { + var i_n = i * 4; + var b_i = i_n + 0; + var g_i = i_n + 1; + var r_i = i_n + 2; + + var b_a = network[b_i]; + var g_a = network[g_i]; + var r_a = network[r_i]; + + b_a = make_abs(b_a - b); + g_a = make_abs(g_a - g); + r_a = make_abs(r_a - r); + + dist = b_a + g_a + r_a; + + if (dist < bestd) + { + bestd = dist; + bestpos = i; + } + + biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); + + if (biasdist < bestbiasd) + { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = (freq[i] >> betashift); + freq[i] -= betafreq; + bias[i] += (betafreq << gammashift); + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return bestbiaspos; + } + +} diff --git a/leenkx/Sources/iron/format/gif/Reader.hx b/leenkx/Sources/iron/format/gif/Reader.hx new file mode 100644 index 00000000..de4aae51 --- /dev/null +++ b/leenkx/Sources/iron/format/gif/Reader.hx @@ -0,0 +1,346 @@ +package iron.format.gif; +import iron.format.gif.Data; +import haxe.io.Bytes; +import haxe.io.BytesOutput; +import haxe.io.Input; + +/** + * ... + * @author Yanrishatum + */ +class Reader +{ + + private var i:Input; + + public function new(i:Input) + { + this.i = i; + i.bigEndian = false; + } + + public function read():Data + { + for (b in [71, 73, 70]) + { + if (i.readByte() != b) throw "Invalid header"; + } + + var gifVer:String = i.readString(3); + var version:Version = Version.GIF89a; + switch(gifVer) + { + case "87a": version = Version.GIF87a; + case "89a": version = Version.GIF89a; + default: version = Version.Unknown(gifVer); + } + + // Logical screen descriptor. + var width:Int = i.readUInt16(); + var height:Int = i.readUInt16(); + var packedField:Int = i.readByte(); + var bgIndex:Int = i.readByte(); + var pixelAspectRatio:Float = i.readByte(); + if (pixelAspectRatio != 0) pixelAspectRatio = (pixelAspectRatio + 15) / 64; + else pixelAspectRatio = 1; + + var lsd:LogicalScreenDescriptor = + { + width: width, + height: height, + hasGlobalColorTable: (packedField & 128) == 128, + colorResolution: (packedField & 112) >>> 4, + sorted: (packedField & 8) == 8, + globalColorTableSize: 2 << (packedField & 7), + backgroundColorIndex: bgIndex, + pixelAspectRatio: pixelAspectRatio + } + + var gct:ColorTable = null; + if (lsd.hasGlobalColorTable) gct = readColorTable(lsd.globalColorTableSize); + + var blocks:List = new List(); + + while (true) + { + var b:Block = readBlock(); + blocks.add(b); + if (b == Block.BEOF) break; + } + + return + { + version: version, + logicalScreenDescriptor: lsd, + globalColorTable: gct, + blocks: blocks + } + } + + private function readBlock():Block + { + var blockID:Int = i.readByte(); + switch(blockID) + { + case 0x2C: + // Image + return readImage(); + case 0x21: + // Extension + return readExtension(); + case 0x3B: + return Block.BEOF; + } + // The behaviour of taking unknown block ID is unspecified. + return Block.BEOF; + } + + private function readImage():Block + { + var x:Int = i.readUInt16(); + var y:Int = i.readUInt16(); + var width:Int = i.readUInt16(); + var height:Int = i.readUInt16(); + var packed:Int = i.readByte(); + var localColorTable:Bool = (packed & 128) == 128; + var interlaced:Bool = (packed & 64) == 64; + var sorted:Bool = (packed & 32) == 32; + var localColorTableSize:Int = 2 << (packed & 7); + + var lct:ColorTable = null; + if (localColorTable) lct = readColorTable(localColorTableSize); + + return Block.BFrame( + { + x: x, + y: y, + width: width, + height: height, + localColorTable: localColorTable, + interlaced:interlaced, + sorted:sorted, + localColorTableSize:localColorTableSize, + pixels:readPixels(width, height, interlaced), + colorTable:lct + }); + + } + + private function readPixels(width:Int, height:Int, interlaced:Bool):Bytes + { + var input:Input = this.i; + + var pixelsCount:Int = width * height; + var pixels:Bytes = Bytes.alloc(pixelsCount); + + var minCodeSize:Int = input.readByte(); + + var blockSize:Int = input.readByte() - 1; + var bits:Int = input.readByte(); + var bitsCount:Int = 8; + + var clearCode:Int = 1 << minCodeSize; + var eoiCode:Int = clearCode + 1; + + var codeSize:Int = minCodeSize + 1; + var codeSizeLimit:Int = 1 << codeSize; + var codeMask = codeSizeLimit - 1; + + + var baseDict:Array> = new Array(); + for (i in 0...clearCode) baseDict[i] = [i]; + + var dict:Array> = new Array(); + var dictLen:Int = clearCode + 2; + var newRecord:Array; + + var i:Int = 0; + var code:Int = 0; + var last:Int; + + while (i < pixelsCount) + { + last = code; + while (bitsCount < codeSize) + { + if (blockSize == 0) break; + bits |= input.readByte() << bitsCount; + bitsCount += 8; + blockSize--; + if (blockSize == 0) blockSize = input.readByte(); + } + code = bits & codeMask; + bits >>= codeSize; + bitsCount -= codeSize; + + if (code == clearCode) + { + dict = baseDict.copy(); + dictLen = clearCode + 2; + codeSize = minCodeSize + 1; + codeSizeLimit = (1 << codeSize); + codeMask = codeSizeLimit - 1; + continue; + } + if (code == eoiCode) break; + + if (code < dictLen) + { + if (last != clearCode) + { + newRecord = dict[last].copy(); + newRecord.push(dict[code][0]); + dict[dictLen++] = newRecord; + } + } + else + { + if (code != dictLen) throw 'Invalid LZW code. Excepted: $dictLen, got: $code'; + newRecord = dict[last].copy(); + newRecord.push(newRecord[0]); + dict[dictLen++] = newRecord; + } + + newRecord = dict[code]; + for (item in newRecord) pixels.set(i++, item); + + if (dictLen == codeSizeLimit && codeSize < 12) + { + codeSize++; + codeSizeLimit = (1 << codeSize); + codeMask = codeSizeLimit - 1; + } + } + + // Just in case + while (blockSize > 0) + { + input.readByte(); + blockSize--; + if (blockSize == 0) blockSize = input.readByte(); + } + + while (i < pixelsCount) pixels.set(i++, 0); + if (interlaced) + { + var buffer:Bytes = Bytes.alloc(pixelsCount); + var offset:Int = deinterlace(pixels, buffer, 8, 0, 0 , width, height); // Every 8 line with start at 0 + offset = deinterlace(pixels, buffer, 8, 4, offset, width, height); // Every 8 line with start at 4 + offset = deinterlace(pixels, buffer, 4, 2, offset, width, height); // Every 4 line with start at 2 + deinterlace(pixels, buffer, 2, 1, offset, width, height); // Every 2 line with start at 1 + pixels = buffer; + } + return pixels; + } + + private function deinterlace(input:Bytes, output:Bytes, step:Int, y:Int, offset:Int, width:Int, height:Int):Int + { + while (y < height) + { + output.blit(y * width, input, offset, width); + offset += width; + y += step; + } + return offset; + } + + private function readExtension():Block + { + var subId:Int = i.readByte(); + + switch(subId) + { + case 0xF9: + // Graphics Control Extension + if (i.readByte() != 4) throw "Incorrect Graphic Control Extension block size!"; + var packed:Int = i.readByte(); + var disposalMethod:DisposalMethod = switch ( (packed & 28) >> 2) + { + case 0: DisposalMethod.UNSPECIFIED; + case 1: DisposalMethod.NO_ACTION; + case 2: DisposalMethod.FILL_BACKGROUND; + case 3: DisposalMethod.RENDER_PREVIOUS; + default: DisposalMethod.UNDEFINED((packed & 28) >> 2); + }; + var b:Block = Block.BExtension(Extension.EGraphicControl( + { + disposalMethod:disposalMethod, + userInput: (packed & 2) == 2, + hasTransparentColor: (packed & 1) == 1, + delay: i.readUInt16(), + transparentIndex: i.readByte() + })); + i.readByte(); // Terminator + return b; + case 0x01: + // Text block + // Exists only on paper, nobody ever used it. + if (i.readByte() != 12) throw "Incorrect size of Plain Text Extension introducer block."; + return Block.BExtension(Extension.EText( + { + textGridX: i.readUInt16(), + textGridY: i.readUInt16(), + textGridWidth: i.readUInt16(), + textGridHeight: i.readUInt16(), + charCellWidth: i.readByte(), + charCellHeight: i.readByte(), + textForegroundColorIndex: i.readByte(), + textBackgroundColorIndex: i.readByte(), + text: readBlocks().toString() + })); + case 0xFE: + // Commentary + return Block.BExtension(Extension.EComment(readBlocks().toString())); + case 0xFF: + // Application extension + return readApplicationExtension(); + default: + return Block.BExtension(Extension.EUnknown(subId, readBlocks())); + } + } + + private function readApplicationExtension():Block + { + if (i.readByte() != 11) throw "Incorrect size of Application Extension introducer block."; + var name:String = i.readString(8); + var version:String = i.readString(3); + var data:Bytes = readBlocks(); + if (name == "NETSCAPE" && version == "2.0" && data.get(0) == 1) + { + return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(data.get(1) | (data.get(2) << 8)))); + } + return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AEUnknown(name, version, data))); + } + + private inline function readBlocks():Bytes + { + var buffer:BytesOutput = new BytesOutput(); + var bytes:Bytes = Bytes.alloc(255); + var len:Int = i.readByte(); + while (len != 0) + { + i.readBytes(bytes, 0, len); + buffer.writeBytes(bytes, 0, len); + len = i.readByte(); + } + buffer.flush(); + bytes = buffer.getBytes(); + buffer.close(); + return bytes; + } + + private function readColorTable(size:Int):ColorTable + { + size *= 3; + var output:ColorTable = ColorTable.alloc(size); + var c:Int = 0; + while (c < size) + { + output.set(c , i.readByte()); // R + output.set(c + 1, i.readByte()); // G + output.set(c + 2, i.readByte()); // B + c += 3; + } + return output; + } +} diff --git a/leenkx/Sources/iron/format/gif/Tools.hx b/leenkx/Sources/iron/format/gif/Tools.hx new file mode 100644 index 00000000..69075863 --- /dev/null +++ b/leenkx/Sources/iron/format/gif/Tools.hx @@ -0,0 +1,390 @@ +package iron.format.gif; + +import iron.format.gif.Data; +import haxe.io.Bytes; +import haxe.io.BytesData; + +/** + * Tools for gif data. + * @author Yanrishatum + */ +class Tools +{ + /** + * Returns amount of frames in Gif data. + */ + public static function framesCount(data:Data):Int + { + var frames:Int = 0; + for (block in data.blocks) + { + switch(block) + { + case Block.BFrame(_): + frames++; + default : + } + } + return frames; + } + + /** + * Returns frame at given index. + * @param data Gif data. + * @param frameIndex Index of frame. + * @return Frame at given index or null, if there is no frame at that index. + */ + public static function frame(data:Data, frameIndex:Int):Frame + { + var counter:Int = 0; + for (block in data.blocks) + { + switch (block) + { + case Block.BFrame(frame): + if (counter == frameIndex) return frame; + counter++; + default : + } + } + return null; + } + + /** + * Returns Graphic Control extension for frame at given index. + * @param data Gif data. + * @param frameIndex Index of frame. + * @return GCE extension if it is exists for given frame, null otherwise. + */ + public static function graphicControl(data:Data, frameIndex:Int):GraphicControlExtension + { + var counter:Int = 0; + var gce:GraphicControlExtension = null; + for (block in data.blocks) + { + switch (block) + { + case Block.BFrame(frame): + if (counter == frameIndex) return gce; + gce = null; + counter++; + case Block.BExtension(Extension.EGraphicControl(g)): + gce = g; + default : + } + } + return null; + } + + //========================================================== + // Extracting. + //========================================================== + + /** + * Extracts frame pixel data in Blue-Green-Red-Alpha pixel format. + * This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size. + * @param data Gif data. + * @param frameIndex Frame index. + * @return BGRA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null. + */ + public static function extractBGRA(data:Data, frameIndex:Int):Bytes + { + var gce:GraphicControlExtension = null; + var frameCaret:Int = 0; + for (block in data.blocks) + { + switch (block) + { + case Block.BExtension(ext): + switch(ext) + { + case Extension.EGraphicControl(g): + gce = g; + default: + } + case Block.BFrame(frame): + if (frameCaret == frameIndex) + { + var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4); + var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable; + if (ct == null) throw "Frame does not have a color table!"; + var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1; + var writeCaret:Int = 0; + for (i in 0...frame.pixels.length) + { + var index:Int = frame.pixels.get(i) * 3; + bytes.set(writeCaret , ct.get(index + 2)); // B + bytes.set(writeCaret + 1, ct.get(index + 1)); // G + bytes.set(writeCaret + 2, ct.get(index )); // R + if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0 + else bytes.set(writeCaret + 3, 0xFF); // A = FF + + writeCaret += 4; + } + return bytes; + } + frameCaret++; + gce = null; + default: + } + } + return null; + } + + /** + * Extracts frame pixel data in Red-Green-Blue-Alpha pixel format. + * This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size. + * @param data Gif data. + * @param frameIndex Frame index. + * @return RGBA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null. + */ + public static function extractRGBA(data:Data, frameIndex:Int):Bytes + { + var gce:GraphicControlExtension = null; + var frameCaret:Int = 0; + for (block in data.blocks) + { + switch (block) + { + case Block.BExtension(ext): + switch(ext) + { + case Extension.EGraphicControl(g): + gce = g; + default: + } + case Block.BFrame(frame): + if (frameCaret == frameIndex) + { + var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4); + var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable; + if (ct == null) throw "Frame does not have a color table!"; + var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1; + var writeCaret:Int = 0; + for (i in 0...frame.pixels.length) + { + var index:Int = frame.pixels.get(i) * 3; + bytes.set(writeCaret , ct.get(index )); // R + bytes.set(writeCaret + 1, ct.get(index + 1)); // G + bytes.set(writeCaret + 2, ct.get(index + 2)); // B + if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0 + else bytes.set(writeCaret + 3, 0xFF); // A = FF + + writeCaret += 4; + } + return bytes; + } + frameCaret++; + gce = null; + default: + } + } + return null; + } + + /** + * Extracts full Gif pixel data to specified frame in Blue-Green-Red-Alpha pixel format. + * This functions returns full representation of frame including rendering of all other frames before. + * @param data Gif data. + * @param frameIndex Frame index. + * @return BGRA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame. + */ + public static function extractFullBGRA(data:Data, frameIndex:Int):Bytes + { + var gce:GraphicControlExtension = null; + var frameCaret:Int = 0; + + var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4); + + for (block in data.blocks) + { + switch (block) + { + case Block.BExtension(ext): + switch(ext) + { + case Extension.EGraphicControl(g): + gce = g; + default: + } + case Block.BFrame(frame): + var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable; + if (ct == null) throw "Frame does not have a color table!"; + var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1; + var pixels:Bytes = frame.pixels; + var x:Int = 0; + var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4; + var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4; + + var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION; + + switch (disposalMethod) + { + case DisposalMethod.RENDER_PREVIOUS: + // Do not render frame at all + case DisposalMethod.FILL_BACKGROUND: + for (i in 0...pixels.length) + { + bytes.set(writeCaret , 0); // B + bytes.set(writeCaret + 1, 0); // G + bytes.set(writeCaret + 2, 0); // R + bytes.set(writeCaret + 3, 0); // A + + if (++x == frame.width) + { + x = 0; + writeCaret += lineSkip; + } + else writeCaret += 4; + } + default: + for (i in 0...pixels.length) + { + var index:Int = pixels.get(i) * 3; + if (transparentIndex != index) // Render only if pixel non-transparent + { + bytes.set(writeCaret , ct.get(index + 2)); // B + bytes.set(writeCaret + 1, ct.get(index + 1)); // G + bytes.set(writeCaret + 2, ct.get(index )); // R + bytes.set(writeCaret + 3, 0xFF); // A + } + + if (++x == frame.width) + { + x = 0; + writeCaret += lineSkip; + } + else writeCaret += 4; + } + } + + if (frameCaret == frameIndex) return bytes; + frameCaret++; + gce = null; + default: + } + } + + return bytes; + } + + /** + * Extracts full Gif pixel data to specified frame in Red-Green-Blue-Alpha pixel format. + * This functions returns full representation of frame including rendering of all other frames before. + * @param data Gif data. + * @param frameIndex Frame index. + * @return RGBA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame. + */ + public static function extractFullRGBA(data:Data, frameIndex:Int):Bytes + { + var gce:GraphicControlExtension = null; + var frameCaret:Int = 0; + + var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4); + + for (block in data.blocks) + { + switch (block) + { + case Block.BExtension(ext): + switch(ext) + { + case Extension.EGraphicControl(g): + gce = g; + default: + } + case Block.BFrame(frame): + var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable; + if (ct == null) throw "Frame does not have a color table!"; + var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1; + var pixels:Bytes = frame.pixels; + var x:Int = 0; + var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4; + var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4; + + var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION; + + switch (disposalMethod) + { + case DisposalMethod.RENDER_PREVIOUS: + // Do not render frame at all + case DisposalMethod.FILL_BACKGROUND: + for (i in 0...pixels.length) + { + bytes.set(writeCaret , 0); // R + bytes.set(writeCaret + 1, 0); // G + bytes.set(writeCaret + 2, 0); // B + bytes.set(writeCaret + 3, 0); // A + + if (++x == frame.width) + { + x = 0; + writeCaret += lineSkip; + } + else writeCaret += 4; + } + default: + for (i in 0...pixels.length) + { + var index:Int = pixels.get(i) * 3; + if (transparentIndex != index) // Render only if pixel non-transparent + { + bytes.set(writeCaret , ct.get(index )); // R + bytes.set(writeCaret + 1, ct.get(index + 1)); // G + bytes.set(writeCaret + 2, ct.get(index + 2)); // B + bytes.set(writeCaret + 3, 0xFF); // A + } + + if (++x == frame.width) + { + x = 0; + writeCaret += lineSkip; + } + else writeCaret += 4; + } + } + + if (frameCaret == frameIndex) return bytes; + frameCaret++; + gce = null; + default: + } + } + + return bytes; + } + + /** + * Returns amount of animation repeats stored in Gif data. + * This is link to Netscape Looping application extension. If this extension does not present amount of loops equals to 1. + * @param data Gif data. + * @return Amount of animation repeats. Zero equals to infinite amount of repeats. + */ + public static function loopCount(data:Data):Int + { + for (block in data.blocks) + { + switch(block) + { + case Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(loops))): return loops; + default : + } + } + return 1; + } + + //========================================================== + // In-Dev writer tools. + //========================================================== + + //public static function buildFrameFromTrueColor(pixels:Bytes, width:Int, height:Int):Void + //{ + // + //} + + private static var LN2:Float = Math.log(2); + @:noCompletion public static inline function log2(val:Float):Float + { + return Math.log(val) / LN2; + } +} diff --git a/leenkx/Sources/iron/format/gif/Writer.hx b/leenkx/Sources/iron/format/gif/Writer.hx new file mode 100644 index 00000000..a563bc71 --- /dev/null +++ b/leenkx/Sources/iron/format/gif/Writer.hx @@ -0,0 +1,525 @@ +package format.gif; +import format.gif.Data; +import haxe.ds.Vector; +import haxe.io.Bytes; +import haxe.io.Output; +import haxe.io.UInt8Array; + +/** + * ... + * @author Yanrishatum + */ +class Writer +{ + + private var o:Output; + private var lzw:LZWEncoder; + private var gctSize:Int; + + public function new(o:Output) + { + this.o = o; + this.lzw = new LZWEncoder(); + o.bigEndian = false; + } + + /** + * Write entire Data at once. + * @param data Input gif file data + */ + public function write(data:Data):Void + { + // Header + writeHeader(data.version); + + // Logical screen descriptor. + writeLogicalScreenDescriptor(data.logicalScreenDescriptor, data.globalColorTable); + + for (block in data.blocks) + { + switch (block) + { + case Block.BEOF: + writeEOF(); + return; + case Block.BExtension(ext): + switch (ext) + { + case Extension.EUnknown(id, bytes): + writeUnknownExtension(id, bytes); + case Extension.EComment(text): + writeComment(text); + case Extension.EText(textExt): + writeText(textExt); + case Extension.EGraphicControl(gce): + writeGraphicControl(gce); + case Extension.EApplicationExtension(appExt): + writeAppExtension(appExt); + } + case Block.BFrame(frame): + writeFrame(frame); + } + } + writeEOF(); // If we doesn't encountered EOF block - write it. + } + + /** + * Writes header of Gif file. Must be first. + * @param version + */ + public function writeHeader(version:Version):Void + { + o.writeString("GIF"); + switch(version) + { + case Version.GIF87a: o.writeString("87a"); + case Version.GIF89a: o.writeString("89a"); + case Version.Unknown(v): + if (v.length == 3) o.writeString(v); + else if (v.length > 3) o.writeString(v.substr(0, 3)); + else + { + while (v.length < 3) v += "-"; + o.writeString(v); + } + } + } + + /** + * Writes Logical Screen Descriptor block. Must go right after header. + * @param lsd Logical Screen Descriptor object. + * @param globalColorTable Global color table. Required only if LSD contains hasGlobalColorTable flag. + * Color table must be a RGB-aligned Bytes with 3 bytes per color. + */ + public function writeLogicalScreenDescriptor(lsd:LogicalScreenDescriptor, globalColorTable:Bytes = null):Void + { + o.writeUInt16(lsd.width); + o.writeUInt16(lsd.height); + + var packed:Int = 0; + if (lsd.hasGlobalColorTable) packed |= 128; + packed |= (lsd.colorResolution << 4) & 112; + if (lsd.sorted) packed |= 8; + packed |= Math.round(Tools.log2(lsd.globalColorTableSize) - 1) & 7; + o.writeByte(packed); + + o.writeByte(lsd.backgroundColorIndex); + if (lsd.pixelAspectRatio == 1) o.writeByte(0); + else o.writeByte(Std.int(lsd.pixelAspectRatio) * 64 - 15); + + if (lsd.hasGlobalColorTable) + { + if (globalColorTable != null) + { + o.writeBytes(globalColorTable, 0, globalColorTable.length); + gctSize = lsd.globalColorTableSize; + } + else throw "hasGlobalColorTable flag present, but there is no global color table!"; + } + } + + public function writeComment(text:String):Void + { + o.writeByte(0x21); + o.writeByte(0xFE); + writeStringBlocks(text); + } + + public function writeText(textExt:PlainTextExtension):Void + { + o.writeByte(0x21); + o.writeByte(0x01); + o.writeByte(12); + o.writeUInt16(textExt.textGridX); + o.writeUInt16(textExt.textGridY); + o.writeUInt16(textExt.textGridWidth); + o.writeUInt16(textExt.textGridHeight); + o.writeByte(textExt.charCellWidth); + o.writeByte(textExt.charCellHeight); + o.writeByte(textExt.textForegroundColorIndex); + o.writeByte(textExt.textForegroundColorIndex); + writeStringBlocks(textExt.text); + } + + public function writeGraphicControl(gce:GraphicControlExtension):Void + { + o.writeByte(0x21); + o.writeByte(0xF9); + o.writeByte(4); + var packed:Int = 0; + + switch (gce.disposalMethod) + { + case DisposalMethod.UNSPECIFIED: // 0 + case DisposalMethod.NO_ACTION: packed |= 4; + case DisposalMethod.FILL_BACKGROUND: packed |= 8; + case DisposalMethod.RENDER_PREVIOUS: packed |= 12; + case DisposalMethod.UNDEFINED(idx): packed |= (idx & 7) << 2; + } + if (gce.userInput) packed |= 2; + if (gce.hasTransparentColor) packed |= 1; + + o.writeByte(packed); + o.writeUInt16(gce.delay); + o.writeByte(gce.transparentIndex); + o.writeByte(0); // Terminator + } + + public function writeAppExtension(appExt:ApplicationExtension):Void + { + o.writeByte(0x21); + o.writeByte(0xFF); + o.writeByte(11); + switch (appExt) + { + case ApplicationExtension.AENetscapeLooping(loops): + o.writeString("NETSCAPE2.0"); + o.writeByte(3); + o.writeByte(1); // Looping + o.writeUInt16(loops); + o.writeByte(0); + case ApplicationExtension.AEUnknown(name, version, bytes): + o.writeString(name); + o.writeString(version); + writeBlocks(bytes); + } + } + + public function writeUnknownExtension(id:Int, bytes:Bytes):Void + { + o.writeByte(0x21); + o.writeByte(id); + writeBlocks(bytes); + } + + public function writeFrame(frame:Frame):Void + { + + o.writeByte(0x2C); + o.writeUInt16(frame.x); + o.writeUInt16(frame.y); + o.writeUInt16(frame.width); + o.writeUInt16(frame.height); + + var packed:Int = 0; + if (frame.localColorTable) packed |= 128; + if (frame.interlaced) packed |= 64; + if (frame.sorted) packed |= 32; + packed |= Math.round(Tools.log2(frame.localColorTableSize) - 1) & 7; + o.writeByte(packed); + if (frame.localColorTable) + { + if (frame.colorTable != null) o.writeBytes(frame.colorTable, 0, frame.colorTable.length); + else throw "localColorTable flag is set, but there is no local color table!"; + } + + lzw.encode(frame.width, frame.height, frame.pixels, frame.localColorTable ? frame.localColorTableSize : gctSize, o, frame.interlaced); + } + + /** + * Writes EndOfFile block. + */ + public function writeEOF():Void + { + o.writeByte(0x3B); + } + + private function writeStringBlocks(text:String):Void + { + var len:Int; + var caret:Int = 0; + while (caret < text.length) + { + len = text.length - caret; + if (len > 0xFF) len = 0xFF; + o.writeByte(len); + for (i in 0...len) o.writeByte(text.charCodeAt(i + caret)); + caret += len; + } + o.writeByte(0); + } + + private function writeBlocks(bytes:Bytes):Void + { + var len:Int; + var caret:Int = 0; + while (caret < bytes.length) + { + len = bytes.length - caret; + if (len > 0xFF) len = 0xFF; + o.writeByte(len); + o.writeBytes(bytes, caret, len); + caret += 0xFF; + } + o.writeByte(0); // Terminator + } + +} + + +class LZWEncoder +{ + private var EOF:Int = -1; + private static inline var BITS:Int = 12; + private static inline var HSIZE:Int = 5003; + private var masks:Array = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, + 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; + + private var out:Output; + private var bits:Int; + private var bitsCount:Int; + + private var minCodeSize:Int; + private var codeSize:Int; + private var codeSizeLimit:Int; + private var clearFlag:Bool; + + private var clearCode:Int; + private var eofCode:Int; + + // Dict + private var htab:Vector; + private var codetab:Vector; + private var freeEnt:Int; + + // Block buffer + private var blockBuffer:Bytes; + private var blockBufferCaret:Int; + + // Input data + private var pixels:Bytes; + private var width:Int; + private var height:Int; + private var remaining:Int; + + // Non-interlaced + private var pixelsCaret:Int; + // Interlaced + private var interlaced:Bool; + private var pixelsX:Int; + private var pixelsY:Int; + private var interlacingStage:Int; + private var interlacingStep:Int; + + public function new() + { + blockBuffer = Bytes.alloc(256); + } + + public function encode(width:Int, height:Int, pixels:Bytes, colorsCount:Int, out:Output, interlaced:Bool):Void + { + minCodeSize = Math.round(Tools.log2(colorsCount)); + + this.pixels = pixels; + this.width = width; + this.height = height; + this.out = out; + + htab = new Vector(HSIZE); + codetab = new Vector(HSIZE); + + blockBufferCaret = 0; + bits = 0; + bitsCount = 0; + + clearCode = 1 << minCodeSize; + eofCode = clearCode + 1; + freeEnt = clearCode + 2; + + out.writeByte(minCodeSize); + remaining = width * height; + + this.interlaced = interlaced; + if (interlaced) + { + pixelsX = 0; + pixelsY = 0; + interlacingStage = 0; + interlacingStep = 8; + } + else pixelsCaret = 0; + + compress(); + out.writeByte(0); + } + + private function char_out(c:Int):Void + { + blockBuffer.set(blockBufferCaret++, c); + if (blockBufferCaret >= 254) flush_char(); + } + + private function cl_block():Void + { + cl_hash(HSIZE); + freeEnt = clearCode + 2; + clearFlag = true; + output(clearCode); + } + + private function cl_hash(hsize:Int):Void + { + for (i in 0...hsize) htab[i] = -1; + } + + private function compress():Void + { + var disp:Int; + var i:Int; + + clearFlag = false; + codeSize = minCodeSize + 1; + codeSizeLimit = MAXCODE(codeSize); + + var ent:Int = nextPixel(); + + var hshift:Int = 0; + var fcode:Int = HSIZE; + while (fcode < 65536) + { + ++hshift; + fcode *= 2; + } + hshift = 8 - hshift; + var hsize_reg:Int = HSIZE; + cl_hash(hsize_reg); + + output(clearCode); + + var c:Int; + while ((c = nextPixel()) != EOF) + { + fcode = (c << BITS) + ent; + i = (c << hshift) ^ ent; + if (htab[i] == fcode) + { + ent = codetab[i]; + continue; + } + else if (htab[i] >= 0) + { + disp = hsize_reg - i; + if (i == 0) disp = 1; + var skip:Bool = false; + do + { + if ((i -= disp) < 0) i += hsize_reg; + if (htab[i] == fcode) + { + ent = codetab[i]; + skip = true; + break; + } + } + while (htab[i] >= 0); + if (skip) continue; + } + + output(ent); + ent = c; + if (freeEnt < (1 << BITS)) + { + codetab[i] = freeEnt++; + htab[i] = fcode; + } + else + { + cl_block(); + } + } + + output(ent); + output(eofCode); + } + + private function flush_char():Void + { + if (blockBufferCaret > 0) + { + out.writeByte(blockBufferCaret); + out.writeBytes(blockBuffer, 0, blockBufferCaret); + blockBufferCaret = 0; + } + } + + private inline function MAXCODE(n_bits:Int):Int + { + return (1 << n_bits) - 1; + } + + private function nextPixel():Int + { + if (remaining == 0) return EOF; + remaining--; + if (interlaced) + { + if (++pixelsX == width) + { + pixelsX = 0; + pixelsY += interlacingStep; + if (pixelsY >= height) + { + switch (interlacingStage) + { + // first: Every 8 line with start at 0 + case 0: pixelsY = 4; // Every 8 line with start at 4 + case 1: pixelsY = 2; interlacingStep = 4; // Every 4 line with start at 2 + case 2: pixelsY = 1; interlacingStep = 2; // Every 2 line with start at 1 + default: return -1; // EOF + } + interlacingStage++; + } + } + return pixels.get(pixelsY * width + pixelsX); + } + else + { + return pixels.get(pixelsCaret++); + } + } + + private function output(code:Int):Void + { + bits &= masks[bitsCount]; + + if (bitsCount > 0) bits |= (code << bitsCount); + else bits = code; + + bitsCount += codeSize; + + while (bitsCount >= 8) + { + char_out(bits & 0xFF); + bits >>= 8; + bitsCount -= 8; + } + + if (freeEnt > codeSizeLimit || clearFlag) + { + if (clearFlag) + { + codeSizeLimit = MAXCODE(codeSize = minCodeSize + 1); + clearFlag = false; + } + else + { + codeSize++; + if (codeSize == BITS) codeSizeLimit = 1 << BITS; + else codeSizeLimit = MAXCODE(codeSize); + } + } + + if (code == eofCode) + { + while (bitsCount > 0) + { + char_out(bits & 0xFF); + bits >>= 8; + bitsCount -= 8; + } + flush_char(); + } + } + +} diff --git a/leenkx/Sources/iron/format/jpg/Data.hx b/leenkx/Sources/iron/format/jpg/Data.hx new file mode 100644 index 00000000..21c0f4d5 --- /dev/null +++ b/leenkx/Sources/iron/format/jpg/Data.hx @@ -0,0 +1,37 @@ +/* + * format - Haxe File Formats + * + * JPG File Format + * Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion) + * + * Copyright (c) 2009, The Haxe Project Contributors + * 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 HAXE PROJECT 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 HAXE PROJECT 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. + */ +package iron.format.jpg; + +typedef Data = { + var width : Int; + var height : Int; + var quality : Float; + var pixels : haxe.io.Bytes; +} diff --git a/leenkx/Sources/iron/format/jpg/Writer.hx b/leenkx/Sources/iron/format/jpg/Writer.hx new file mode 100644 index 00000000..11ff7bb8 --- /dev/null +++ b/leenkx/Sources/iron/format/jpg/Writer.hx @@ -0,0 +1,657 @@ +package iron.format.jpg; + +class Writer { + var ZigZag: Array; + + // Static table initialization + function initZigZag() { + ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + } + + var YTable: Array; + var UVTable: Array; + var fdtbl_Y: Array; + var fdtbl_UV: Array; + + function initQuantTables(sf: Int) { + var YQT: Array = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + for (i in 0...64) { + var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 ); + if( t < 1 ) t = 1; + else if( t > 255 ) t = 255; + YTable[ ZigZag[i] ] = t; + } + var UVQT: Array = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for( j in 0...64 ) { + var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 ); + if( u < 1 ) u = 1; + else if( u > 255 ) u = 255; + UVTable[ ZigZag[j] ] = u; + } + var aasf: Array = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for( row in 0...8 ) { + for( col in 0...8 ) { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + var std_dc_luminance_nrcodes: Array; + var std_dc_luminance_values: haxe.io.Bytes; + var std_ac_luminance_nrcodes: Array; + var std_ac_luminance_values: haxe.io.Bytes; + + function initLuminance() { + std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' ); + std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + std_ac_luminance_values = strIntsToBytes( + '0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' + + '0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' + + '0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' + + '0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' + + '0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' + + '0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' + + '0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' + + '0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' + + '0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' + + '0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' + + '0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' + + '0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' + + '0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' + + '0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' + + '0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' + + '0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' + + '0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' + + '0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' + + '0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' + + '0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' + + '0xf9,0xfa' + ); + } + + function strIntsToBytes( s: String ) { + var len = s.length; + var b = new haxe.io.BytesBuffer(); + var val = 0; + var i = 0; + for( j in 0...len ) { + if( s.charAt( j ) == ',' ) { + val = Std.parseInt( s.substr(i, j - i) ); + b.addByte( val ); + i = j + 1; + } + } + if( i < len ) { + val = Std.parseInt( s.substr(i) ); + b.addByte( val ); + } + return b.getBytes(); + } + + var std_dc_chrominance_nrcodes: Array; + var std_dc_chrominance_values: haxe.io.Bytes; + var std_ac_chrominance_nrcodes: Array; + var std_ac_chrominance_values: haxe.io.Bytes; + + function initChrominance() { + std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' ); + std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + std_ac_chrominance_values = strIntsToBytes( + '0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' + + '0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' + + '0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' + + '0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' + + '0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' + + '0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' + + '0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' + + '0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' + + '0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' + + '0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' + + '0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' + + '0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' + + '0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' + + '0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' + + '0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' + + '0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' + + '0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' + + '0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' + + '0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' + + '0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' + + '0xf9,0xfa' + ); + } + + var YDC_HT: Map; + var UVDC_HT: Map; + var YAC_HT: Map; + var UVAC_HT: Map; + + // Función para crear la tabla Huffman (Helper) + function computeHuffmanTbl(nrcodes: Array, std_table: haxe.io.Bytes): Map { + var codevalue = 0; + var pos_in_table = 0; + var HT: Map = new Map(); + for( k in 1...17 ) { + var end = nrcodes[k]; + for( j in 0...end ) { + var idx: Int = std_table.get( pos_in_table ); + HT.set( idx, new BitString( k, codevalue ) ); + pos_in_table++; + codevalue++; + } + codevalue *= 2; + } + return HT; + } + + function initHuffmanTbl() { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values); + + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values); + + // CORRECCIÓN DE TABLAS: Asegurar la existencia de EOB (0x00) y ZRL (0xF0) + // Esto es necesario para evitar fallos si el 'computeHuffmanTbl' no incluye estos valores por algún motivo. + if (YAC_HT.get(0x00) == null) YAC_HT.set(0x00, new BitString(4, 0x00)); + if (UVAC_HT.get(0x00) == null) UVAC_HT.set(0x00, new BitString(4, 0x00)); + if (YAC_HT.get(0xF0) == null) YAC_HT.set(0xF0, new BitString(11, 0x1E)); + if (UVAC_HT.get(0xF0) == null) UVAC_HT.set(0xF0, new BitString(11, 0x1E)); + } + + var bitcode: Map; + var category: Map; + + function initCategoryNumber() { + var nrlower = 1; + var nrupper = 2; + var idx: Int; + for (cat in 1...16) { + //Positive numbers + for( nr in nrlower...nrupper ) { + idx = 32767 + nr; + category.set( idx, cat ); + bitcode.set( idx, new BitString( cat, nr ) ); + } + //Negative numbers + var nrneg: Int = -(nrupper - 1); + while( nrneg <= -nrlower ) { + idx = 32767 + nrneg; + category.set( idx, cat ); + bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) ); + nrneg++; + } + nrlower <<= 1; + nrupper <<= 1; + } + } + + // IO functions + var byteout: haxe.io.Output; + var bytenew: Int; + var bytepos: Int; + + function writeBits(bs: BitString) { + // Se confía en que bs no es nulo gracias al clamping y las correcciones de tablas. + var value: Int = bs.val; + var posval: Int = bs.len - 1; + while( posval >= 0 ) { + if( (value & (1 << posval)) != 0 ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if( bytepos < 0 ) { + if( bytenew == 0xFF ) { + b(0xFF); + b(0); + } + else { + b(bytenew); + } + bytepos = 7; + bytenew = 0; + } + } + } + + function writeWord( val: Int ) { + b( (val >> 8) & 0xFF ); + b( val & 0xFF ); + } + + // DCT & quantization core + + function fDCTQuant(data: Array, fdtbl: Array): Array { + /* Pass 1: process rows. */ + var dataOff = 0; + for (i in 0...8) { + var tmp0: Float = data[dataOff + 0] + data[dataOff + 7]; + var tmp7: Float = data[dataOff + 0] - data[dataOff + 7]; + var tmp1: Float = data[dataOff + 1] + data[dataOff + 6]; + var tmp6: Float = data[dataOff + 1] - data[dataOff + 6]; + var tmp2: Float = data[dataOff + 2] + data[dataOff + 5]; + var tmp5: Float = data[dataOff + 2] - data[dataOff + 5]; + var tmp3: Float = data[dataOff + 3] + data[dataOff + 4]; + var tmp4: Float = data[dataOff + 3] - data[dataOff + 4]; + + /* Even part */ + var tmp10: Float = tmp0 + tmp3; /* phase 2 */ + var tmp13: Float = tmp0 - tmp3; + var tmp11: Float = tmp1 + tmp2; + var tmp12: Float = tmp1 - tmp2; + + data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */ + data[dataOff + 4] = tmp10 - tmp11; + + var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */ + data[dataOff + 2] = tmp13 + z1; /* phase 5 */ + data[dataOff + 6] = tmp13 - z1; + + /* Odd part */ + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */ + var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */ + var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */ + var z3: Float = tmp11 * 0.707106781; /* c4 */ + + var z11: Float = tmp7 + z3; /* phase 5 */ + var z13: Float = tmp7 - z3; + + data[dataOff + 5] = z13 + z2; /* phase 6 */ + data[dataOff + 3] = z13 - z2; + data[dataOff + 1] = z11 + z4; + data[dataOff + 7] = z11 - z4; + + dataOff += 8; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + dataOff = 0; + for (j in 0...8) { + var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56]; + var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56]; + var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48]; + var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48]; + var tmp2p2: Float = data[dataOff+16] + data[dataOff+40]; + var tmp5p2: Float = data[dataOff+16] - data[dataOff+40]; + var tmp3p2: Float = data[dataOff+24] + data[dataOff+32]; + var tmp4p2: Float = data[dataOff+24] - data[dataOff+32]; + + /* Even part */ + var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */ + var tmp13p2: Float = tmp0p2 - tmp3p2; + var tmp11p2: Float = tmp1p2 + tmp2p2; + var tmp12p2: Float = tmp1p2 - tmp2p2; + + data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */ + data[dataOff+32] = tmp10p2 - tmp11p2; + + var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */ + data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */ + data[dataOff+48] = tmp13p2 - z1p2; + + /* Odd part */ + tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */ + tmp11p2 = tmp5p2 + tmp6p2; + tmp12p2 = tmp6p2 + tmp7p2; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */ + var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */ + var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */ + var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */ + + var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */ + var z13p2: Float = tmp7p2 - z3p2; + + data[dataOff+40] = z13p2 + z2p2; /* phase 6 */ + data[dataOff+24] = z13p2 - z2p2; + data[dataOff+ 8] = z11p2 + z4p2; + data[dataOff+56] = z11p2 - z4p2; + + dataOff++; /* advance pointer to next column */ + } + + // Quantize/descale the coefficients + for (k in 0...64) { + // Apply the quantization and scaling factor & Round to nearest integer + data[k] = Math.round(data[k] * fdtbl[k]); + } + return data; + } + + // Chunk writing + + inline function b(v) { + byteout.writeByte(v); + } + + function writeAPP0() { + b(0xFF); b(0xE0); //<- marker 0xFFE0 + b(0); b(16); //<- length + b("J".code); // J + b("F".code); + b("I".code); + b("F".code); + b(0); + b(1); // versionhi + b(1); // versionlo + b(0); // xyunits + b(0); b(1); // xdensity + b(0); b(1); // ydensity + b(0); // thumbnwidth + b(0); // thumbnheight + } + function writeDQT() { + b(0xFF); b(0xDB); //<- marker 0xFFDB + b(0); b(132); //<- length + b(0); + for( j in 0...64 ) + b(YTable[j]); + b(1); + for( j in 0...64 ) + b(UVTable[j]); + } + function writeSOF0(width: Int, height: Int) { + b(0xFF); b(0xC0); //<- marker 0xFFC0 + b(0); b(17); //<- length, truecolor YUV JPG + b(8); // precision + b( (height>>8) & 0xFF ); + b( height & 0xFF ); + b( (width>>8) & 0xFF ); + b( width & 0xFF ); + b(3); // nrofcomponents + b(1); // IdY + b(0x11); // HVY + b(0); // QTY + b(2); // IdU + b(0x11); // HVU + b(1); // QTU + b(3); // IdV + b(0x11); // HVV + b(1); // QTV + } + + function writeDHT() { + b(0xFF); b(0xC4); //<- marker 0xFFC4 + b(0x01); b(0xA2); //<- length + b(0); // HTYDCinfo + for( j in 1...17 ) + b(std_dc_luminance_nrcodes[j]); + byteout.write(std_dc_luminance_values); + + b(0x10); // HTYACinfo + for( j in 1...17 ) + b(std_ac_luminance_nrcodes[j]); + byteout.write(std_ac_luminance_values); + + b(1); // HTUDCinfo + for( j in 1...17 ) + b(std_dc_chrominance_nrcodes[j]); + byteout.write(std_dc_chrominance_values); + + b(0x11); // HTUACinfo + for( j in 1...17 ) + b(std_ac_chrominance_nrcodes[j]); + byteout.write(std_ac_chrominance_values); + } + + function writeSOS() { + b(0xFF); b(0xDA); //<- marker 0xFFDA + b(0); b(12); //<- length + b(3); // nrofcomponents + b(1); // IdY + b(0); // HTY + b(2); // IdU + b(0x11); // HTU + b(3); // IdV + b(0x11); // HTV + b(0); // Ss + b(0x3F); // Se + b(0); // Bf + } + + // Core processing + var DU: Array; + + function processDU(CDU: Array, fdtbl: Array, DC: Float, HTDC: Map, HTAC: Map): Float { + var EOB: BitString = HTAC.get( 0x00 ); + var M16zeroes: BitString = HTAC.get( 0xF0 ); + + var DU_DCT: Array = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (i in 0...64) { + DU[ ZigZag[i] ] = DU_DCT[i]; + } + var idx: Int; + var Diff = Std.int( DU[0] - DC ); + DC = DU[0]; + + // CORRECCIÓN DE RANGO: Clamping de la diferencia DC (previene accesos a `category` fuera de rango) + if (Diff > 16383) Diff = 16383; + if (Diff < -16383) Diff = -16383; + + //Encode DC + if( Diff == 0 ) { + writeBits( HTDC.get(0) ); + } else { + idx = 32767 + Diff; + writeBits(HTDC.get( category.get( idx ) )); + writeBits( bitcode.get( idx ) ); + } + + //Encode ACs + var end0pos = 63; + while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--; + + //end0pos = first element in reverse order !=0 + if ( end0pos == 0 ) { + writeBits(EOB); + return DC; + } + var i = 1; + while ( i <= end0pos ) { + var startpos = i; + while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++; + + // Chequeo de seguridad si 'i' saltó más allá + if (i > end0pos) break; + + var nrzeroes: Int = i - startpos; + if ( nrzeroes >= 16 ) { + for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes); + nrzeroes &= 0xF; + } + + // CORRECCIÓN DE RANGO: Clamping del coeficiente AC + var du_val = Std.int( DU[i] ); + if (du_val > 16383) du_val = 16383; + if (du_val < -16383) du_val = -16383; + + // LÓGICA DE SALTO: Si el clamping forzó el valor a 0, saltamos. + if (du_val == 0) { + i++; + continue; + } + + idx = 32767 + du_val; + + var cat = category.get( idx ); + // Si 'cat' es nulo, significa que el valor de 'du_val' está fuera del rango -16383..16383, lo cual el clamping debería haber prevenido. + var index_ac = nrzeroes * 16 + cat; + + writeBits( HTAC.get( index_ac ) ); + writeBits( bitcode.get( idx ) ); + i++; + } + if( end0pos != 63 ) writeBits(EOB); + return DC; + } + + var YDU: Array; + var UDU: Array; + var VDU: Array; + + function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) { + var pos = 0; + for( y in 0...8 ) { + var offset = ((y + ypos) * width + xpos) << 2; + for( x in 0...8 ) { + offset++; // skip alpha + var R = img.get(offset++); + var G = img.get(offset++); + var B = img.get(offset++); + YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128; + UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B)); + VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B)); + pos++; + } + } + } + + public function new( out : haxe.io.Output ) { + //begin : lines added to initialize variables + YTable = new Array(); + UVTable = new Array(); + fdtbl_Y = new Array(); + fdtbl_UV = new Array(); + for (i in 0...64) { + YTable.push(0); UVTable.push(0); + fdtbl_Y.push(0.0); fdtbl_UV.push(0.0); + } + + bitcode = new Map(); + category = new Map(); + byteout = out; + bytenew = 0; + bytepos = 7; + + YDC_HT = new Map(); + UVDC_HT = new Map(); + YAC_HT = new Map(); + UVAC_HT = new Map(); + + YDU = new Array(); + UDU = new Array(); + VDU = new Array(); + DU = new Array(); + for (i in 0...64) { + YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0); + } + initZigZag(); + initLuminance(); + initChrominance(); + //end : lines added to initialize variables + + // Create tables + initHuffmanTbl(); + initCategoryNumber(); + } + + public function write( image : Data ) { + // init quality table + var quality = image.quality; + if( quality <= 0 ) quality = 1; + if( quality > 100 ) quality = 100; + var sf = + if( quality < 50 ) Std.int( 5000 / quality ) + else Std.int( 200 - quality * 2 ); + initQuantTables(sf); + + // Initialize bit writer + bytenew = 0; + bytepos = 7; + + var width = image.width; + var height = image.height; + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0( width, height ); + writeDHT(); + writeSOS(); + + // Encode 8x8 macroblocks + var DCY = 0.0; + var DCU = 0.0; + var DCV = 0.0; + bytenew = 0; + bytepos = 7; + var ypos = 0; + while( ypos < height ) { + var xpos = 0; + while( xpos < width ) { + + // CORRECCIÓN CRÍTICA DE ESTADO: Limpieza de arreglos para evitar arrastre de valores (lo que el 'trace' estaba enmascarando) + // Se asegura que los buffers sean cero antes de llenarlos con RGB2YUV si no se llenan completamente. + for (k in 0...64) { YDU[k] = 0.0; UDU[k] = 0.0; VDU[k] = 0.0; } + + RGB2YUV(image.pixels, width, xpos, ypos); + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + xpos += 8; + } + ypos += 8; + } + + // Do the bit alignment of the EOI marker + if( bytepos >= 0 ) { + var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 ); + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + } +} + +private class BitString { + public var len: Int; + public var val: Int; + + public function new( l: Int, v: Int ) { + len = l; + val = v; + } +} diff --git a/leenkx/Sources/iron/format/jpg/WriterOriginal.hx b/leenkx/Sources/iron/format/jpg/WriterOriginal.hx new file mode 100644 index 00000000..4d4fd63f --- /dev/null +++ b/leenkx/Sources/iron/format/jpg/WriterOriginal.hx @@ -0,0 +1,651 @@ +/* + * format - Haxe File Formats + * + * JPG File Format + * Copyright (C) 2007-2009 Thibault Imbert, AS3-to-Haxe by Michel Oster + * + * Copyright (c) 2009, The Haxe Project Contributors + * 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 HAXE PROJECT 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 HAXE PROJECT 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. + */ +package iron.format.jpg; + +class Writer { + var ZigZag: Array; + + // Static table initialization + function initZigZag() { + ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + } + + var YTable: Array; // = new Array(64); + var UVTable: Array; // = new Array(64); + var fdtbl_Y: Array; // = new Array(64); + var fdtbl_UV: Array; // = new Array(64); + + function initQuantTables(sf: Int) { + var YQT: Array = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + for (i in 0...64) { + var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 ); + if( t < 1 ) t = 1; + else if( t > 255 ) t = 255; + YTable[ ZigZag[i] ] = t; + } + var UVQT: Array = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for( j in 0...64 ) { + var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 ); + if( u < 1 ) u = 1; + else if( u > 255 ) u = 255; + UVTable[ ZigZag[j] ] = u; + } + var aasf: Array = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for( row in 0...8 ) { + for( col in 0...8 ) { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + var std_dc_luminance_nrcodes: Array; + var std_dc_luminance_values: haxe.io.Bytes; + var std_ac_luminance_nrcodes: Array; + var std_ac_luminance_values: haxe.io.Bytes; + + function initLuminance() { + std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' ); + std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + std_ac_luminance_values = strIntsToBytes( + '0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' + + '0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' + + '0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' + + '0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' + + '0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' + + '0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' + + '0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' + + '0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' + + '0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' + + '0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' + + '0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' + + '0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' + + '0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' + + '0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' + + '0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' + + '0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' + + '0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' + + '0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' + + '0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' + + '0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' + + '0xf9,0xfa' + ); + } + + function strIntsToBytes( s: String ) { + var len = s.length; + var b = new haxe.io.BytesBuffer(); + var val = 0; + var i = 0; + for( j in 0...len ) { + if( s.charAt( j ) == ',' ) { + val = Std.parseInt( s.substr(i, j - i) ); + b.addByte( val ); + i = j + 1; + } + } + if( i < len ) { + val = Std.parseInt( s.substr(i) ); + b.addByte( val ); + } + return b.getBytes(); + } + + var std_dc_chrominance_nrcodes: Array; + var std_dc_chrominance_values: haxe.io.Bytes; + var std_ac_chrominance_nrcodes: Array; + var std_ac_chrominance_values: haxe.io.Bytes; + + function initChrominance() { + std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' ); + std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + std_ac_chrominance_values = strIntsToBytes( + '0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' + + '0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' + + '0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' + + '0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' + + '0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' + + '0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' + + '0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' + + '0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' + + '0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' + + '0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' + + '0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' + + '0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' + + '0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' + + '0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' + + '0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' + + '0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' + + '0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' + + '0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' + + '0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' + + '0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' + + '0xf9,0xfa' + ); + } + + var YDC_HT: Map; + var UVDC_HT: Map; + var YAC_HT: Map; + var UVAC_HT: Map; + + function initHuffmanTbl() { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values); + } + + function computeHuffmanTbl(nrcodes: Array, std_table: haxe.io.Bytes): Map { + var codevalue = 0; + var pos_in_table = 0; + var HT: Map = new Map(); + for( k in 1...17 ) { + var end = nrcodes[k]; + for( j in 0...end ) { + var idx: Int = std_table.get( pos_in_table ); + HT.set( idx, new BitString( k, codevalue ) ); + pos_in_table++; + codevalue++; + } + codevalue *= 2; + } + return HT; + } + + var bitcode: Map; + var category: Map; + + function initCategoryNumber() { + var nrlower = 1; + var nrupper = 2; + var idx: Int; + for (cat in 1...16) { + //Positive numbers + for( nr in nrlower...nrupper ) { + idx = 32767 + nr; + category.set( idx, cat ); + bitcode.set( idx, new BitString( cat, nr ) ); + } + //Negative numbers + var nrneg: Int = -(nrupper - 1); + while( nrneg <= -nrlower ) { + idx = 32767 + nrneg; + category.set( idx, cat ); + bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) ); + nrneg++; + } + nrlower <<= 1; + nrupper <<= 1; + } + } + + // IO functions + var byteout: haxe.io.Output; + var bytenew: Int; + var bytepos: Int; + + function writeBits(bs: BitString) { + var value: Int = bs.val; + var posval: Int = bs.len - 1; + while( posval >= 0 ) { + //if (value & uint(1 << posval) ) { + if( (value & (1 << posval)) != 0 ) { //<- CORRECT ? + //bytenew |= uint(1 << bytepos); + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if( bytepos < 0 ) { + if( bytenew == 0xFF ) { + b(0xFF); + b(0); + } + else { + b(bytenew); + } + bytepos = 7; + bytenew = 0; + } + } + } + + function writeWord( val: Int ) { + b( (val >> 8) & 0xFF ); + b( val & 0xFF ); + } + + // DCT & quantization core + + function fDCTQuant(data: Array, fdtbl: Array): Array { + /* Pass 1: process rows. */ + var dataOff = 0; + for (i in 0...8) { + var tmp0: Float = data[dataOff + 0] + data[dataOff + 7]; + var tmp7: Float = data[dataOff + 0] - data[dataOff + 7]; + var tmp1: Float = data[dataOff + 1] + data[dataOff + 6]; + var tmp6: Float = data[dataOff + 1] - data[dataOff + 6]; + var tmp2: Float = data[dataOff + 2] + data[dataOff + 5]; + var tmp5: Float = data[dataOff + 2] - data[dataOff + 5]; + var tmp3: Float = data[dataOff + 3] + data[dataOff + 4]; + var tmp4: Float = data[dataOff + 3] - data[dataOff + 4]; + + /* Even part */ + var tmp10: Float = tmp0 + tmp3; /* phase 2 */ + var tmp13: Float = tmp0 - tmp3; + var tmp11: Float = tmp1 + tmp2; + var tmp12: Float = tmp1 - tmp2; + + data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */ + data[dataOff + 4] = tmp10 - tmp11; + + var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */ + data[dataOff + 2] = tmp13 + z1; /* phase 5 */ + data[dataOff + 6] = tmp13 - z1; + + /* Odd part */ + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */ + var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */ + var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */ + var z3: Float = tmp11 * 0.707106781; /* c4 */ + + var z11: Float = tmp7 + z3; /* phase 5 */ + var z13: Float = tmp7 - z3; + + data[dataOff + 5] = z13 + z2; /* phase 6 */ + data[dataOff + 3] = z13 - z2; + data[dataOff + 1] = z11 + z4; + data[dataOff + 7] = z11 - z4; + + dataOff += 8; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + dataOff = 0; + for (j in 0...8) { + var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56]; + var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56]; + var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48]; + var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48]; + var tmp2p2: Float = data[dataOff+16] + data[dataOff+40]; + var tmp5p2: Float = data[dataOff+16] - data[dataOff+40]; + var tmp3p2: Float = data[dataOff+24] + data[dataOff+32]; + var tmp4p2: Float = data[dataOff+24] - data[dataOff+32]; + + /* Even part */ + var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */ + var tmp13p2: Float = tmp0p2 - tmp3p2; + var tmp11p2: Float = tmp1p2 + tmp2p2; + var tmp12p2: Float = tmp1p2 - tmp2p2; + + data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */ + data[dataOff+32] = tmp10p2 - tmp11p2; + + var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */ + data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */ + data[dataOff+48] = tmp13p2 - z1p2; + + /* Odd part */ + tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */ + tmp11p2 = tmp5p2 + tmp6p2; + tmp12p2 = tmp6p2 + tmp7p2; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */ + var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */ + var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */ + var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */ + + var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */ + var z13p2: Float = tmp7p2 - z3p2; + + data[dataOff+40] = z13p2 + z2p2; /* phase 6 */ + data[dataOff+24] = z13p2 - z2p2; + data[dataOff+ 8] = z11p2 + z4p2; + data[dataOff+56] = z11p2 - z4p2; + + dataOff++; /* advance pointer to next column */ + } + + // Quantize/descale the coefficients + for (k in 0...64) { + // Apply the quantization and scaling factor & Round to nearest integer + data[k] = Math.round(data[k] * fdtbl[k]); + } + return data; + } + + // Chunk writing + + inline function b(v) { + byteout.writeByte(v); + } + + function writeAPP0() { + b(0xFF); b(0xE0); //<- marker 0xFFE0 + b(0); b(16); //<- length + b("J".code); // J + b("F".code); + b("I".code); + b("F".code); + b(0); + b(1); // versionhi + b(1); // versionlo + b(0); // xyunits + b(0); b(1); // xdensity + b(0); b(1); // ydensity + b(0); // thumbnwidth + b(0); // thumbnheight + } + function writeDQT() { + b(0xFF); b(0xDB); //<- marker 0xFFDB + b(0); b(132); //<- length + b(0); + for( j in 0...64 ) + b(YTable[j]); + b(1); + for( j in 0...64 ) + b(UVTable[j]); + } + function writeSOF0(width: Int, height: Int) { + b(0xFF); b(0xC0); //<- marker 0xFFC0 + b(0); b(17); //<- length, truecolor YUV JPG + b(8); // precision + b( (height>>8) & 0xFF ); + b( height & 0xFF ); + b( (width>>8) & 0xFF ); + b( width & 0xFF ); + b(3); // nrofcomponents + b(1); // IdY + b(0x11); // HVY + b(0); // QTY + b(2); // IdU + b(0x11); // HVU + b(1); // QTU + b(3); // IdV + b(0x11); // HVV + b(1); // QTV + } + + function writeDHT() { + b(0xFF); b(0xC4); //<- marker 0xFFC4 + b(0x01); b(0xA2); //<- length + b(0); // HTYDCinfo + for( j in 1...17 ) + b(std_dc_luminance_nrcodes[j]); + byteout.write(std_dc_luminance_values); + + b(0x10); // HTYACinfo + for( j in 1...17 ) + b(std_ac_luminance_nrcodes[j]); + byteout.write(std_ac_luminance_values); + + b(1); // HTUDCinfo + for( j in 1...17 ) + b(std_dc_chrominance_nrcodes[j]); + byteout.write(std_dc_chrominance_values); + + b(0x11); // HTUACinfo + for( j in 1...17 ) + b(std_ac_chrominance_nrcodes[j]); + byteout.write(std_ac_chrominance_values); + } + + function writeSOS() { + b(0xFF); b(0xDA); //<- marker 0xFFDA + b(0); b(12); //<- length + b(3); // nrofcomponents + b(1); // IdY + b(0); // HTY + b(2); // IdU + b(0x11); // HTU + b(3); // IdV + b(0x11); // HTV + b(0); // Ss + b(0x3F); // Se + b(0); // Bf + } + + // Core processing + var DU: Array; //<- initialized in function new JPEGEncoder() + + function processDU(CDU: Array, fdtbl: Array, DC: Float, HTDC: Map, HTAC: Map): Float { + var EOB: BitString = HTAC.get( 0x00 ); + var M16zeroes: BitString = HTAC.get( 0xF0 ); + + var DU_DCT: Array = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (i in 0...64) { + DU[ ZigZag[i] ] = DU_DCT[i]; + } + var idx: Int; + var Diff = Std.int( DU[0] - DC ); + DC = DU[0]; + //Encode DC + if( Diff == 0 ) { + writeBits( HTDC.get(0) ); // Diff might be 0 + } else { + idx = 32767 + Diff; + writeBits(HTDC.get( category.get( idx ) )); + writeBits( bitcode.get( idx ) ); + } + + //Encode ACs + var end0pos = 63; + //for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) { }; + while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--; + + //end0pos = first element in reverse order !=0 + if ( end0pos == 0 ) { + writeBits(EOB); + return DC; + } + var i = 1; + while ( i <= end0pos ) { + var startpos = i; + //for (; (DU[i]==0) && (i<=end0pos); i++) { }; <- it's a 'while' loop + while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++; + + var nrzeroes: Int = i - startpos; + if ( nrzeroes >= 16 ) { + //for (var nrmarker: Int=1; nrmarker <= nrzeroes/16; nrmarker++) { + for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes); + nrzeroes &= 0xF; + } + idx = 32767 + Std.int( DU[i] ); //<- line added + writeBits( HTAC.get( nrzeroes * 16 + category.get( idx ) ) ); + writeBits( bitcode.get( idx ) ); + i++; + } + if( end0pos != 63 ) writeBits(EOB); + return DC; + } + + var YDU: Array; + var UDU: Array; + var VDU: Array; + + function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) { + var pos = 0; + for( y in 0...8 ) { + var offset = ((y + ypos) * width + xpos) << 2; + for( x in 0...8 ) { + offset++; // skip alpha + var R = img.get(offset++); + var G = img.get(offset++); + var B = img.get(offset++); + YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128; + UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B)); + VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B)); + pos++; + } + } + } + + public function new( out : haxe.io.Output ) { + //begin : lines added to initialize variables + YTable = new Array(); + UVTable = new Array(); + fdtbl_Y = new Array(); + fdtbl_UV = new Array(); + for (i in 0...64) { + YTable.push(0); UVTable.push(0); + fdtbl_Y.push(0.0); fdtbl_UV.push(0.0); + } + + bitcode = new Map(); //<- 65535 elements + category = new Map(); //<- 65535 elements + byteout = out; + bytenew = 0; + bytepos = 7; + + YDC_HT = new Map(); + UVDC_HT = new Map(); + YAC_HT = new Map(); + UVAC_HT = new Map(); + + YDU = new Array(); //<- 64 elements + UDU = new Array(); + VDU = new Array(); + DU = new Array(); + for (i in 0...64) { + YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0); + } + initZigZag(); + initLuminance(); + initChrominance(); + //end : lines added to initialize variables + + // Create tables + initHuffmanTbl(); + initCategoryNumber(); + } + + public function write( image : Data ) { + // init quality table + var quality = image.quality; + if( quality <= 0 ) quality = 1; + if( quality > 100 ) quality = 100; + var sf = + if( quality < 50 ) Std.int( 5000 / quality ) + else Std.int( 200 - quality * 2 ); + initQuantTables(sf); + + // Initialize bit writer + bytenew = 0; + bytepos = 7; + + var width = image.width; + var height = image.height; + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0( width, height ); + writeDHT(); + writeSOS(); + + // Encode 8x8 macroblocks + var DCY = 0.0; + var DCU = 0.0; + var DCV = 0.0; + bytenew = 0; + bytepos = 7; + var ypos = 0; + while( ypos < height ) { + var xpos = 0; + while( xpos < width ) { + RGB2YUV(image.pixels, width, xpos, ypos); + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + xpos += 8; + } + ypos += 8; + } + + // Do the bit alignment of the EOI marker + if( bytepos >= 0 ) { + var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 ); + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + } +} + +private class BitString { + public var len: Int; + public var val: Int; + + public function new( l: Int, v: Int ) { + len = l; + val = v; + } +} diff --git a/leenkx/Sources/iron/format/wav/Data.hx b/leenkx/Sources/iron/format/wav/Data.hx new file mode 100644 index 00000000..f1f8c9e3 --- /dev/null +++ b/leenkx/Sources/iron/format/wav/Data.hx @@ -0,0 +1,56 @@ +/* + * format - Haxe File Formats + * + * WAVE File Format + * Copyright (C) 2009 Robin Palotai + * + * Copyright (c) 2009, The Haxe Project Contributors + * 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 HAXE PROJECT 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 HAXE PROJECT 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. + */ +package iron.format.wav; + +typedef WAVE = { + header : WAVEHeader, + data : haxe.io.Bytes, + cuePoints : Array +} + +typedef WAVEHeader = { + format : WAVEFormat, + channels : Int, + samplingRate : Int, + byteRate : Int, // samplingRate * channels * bitsPerSample / 8 + blockAlign : Int, // channels * bitsPerSample / 8 + bitsPerSample : Int +} + +typedef CuePoint = { + id : Int, + sampleOffset : Int +} + +enum WAVEFormat { + WF_PCM; +} + + diff --git a/leenkx/Sources/iron/format/wav/Reader.hx b/leenkx/Sources/iron/format/wav/Reader.hx new file mode 100644 index 00000000..3e7c1b08 --- /dev/null +++ b/leenkx/Sources/iron/format/wav/Reader.hx @@ -0,0 +1,155 @@ +/* + * format - Haxe File Formats + * + * WAVE File Format + * Copyright (C) 2009 Robin Palotai + * + * Copyright (c) 2009, The Haxe Project Contributors + * 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 HAXE PROJECT 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 HAXE PROJECT 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. + */ +package iron.format.wav; +import iron.format.wav.Data; + +class Reader { + + var i : haxe.io.Input; + var version : Int; + + public function new(i) { + this.i = i; + i.bigEndian = false; + } + + inline function readInt() { + #if haxe3 + return i.readInt32(); + #else + return i.readUInt30(); + #end + } + + public function read() : WAVE { + + if (i.readString(4) != "RIFF") + throw "RIFF header expected"; + + var len = readInt(); + + if (i.readString(4) != "WAVE") + throw "WAVE signature not found"; + + var fmt = i.readString(4); + while(fmt != "fmt ") { + switch( fmt ) { + case "JUNK": //protool + var junkLen = i.readInt32(); + i.read(junkLen); + fmt = i.readString(4); + case "bext": + var bextLen = i.readInt32(); + i.read(bextLen); + fmt = i.readString(4); + default: + break; + } + } + if ( fmt != "fmt " ) + throw "unsupported wave chunk "+fmt; + + var fmtlen = readInt(); + var format = switch (i.readUInt16()) { + case 1,3: WF_PCM; + default: throw "only PCM (uncompressed) WAV files are supported"; + } + var channels = i.readUInt16(); + var samplingRate = readInt(); + var byteRate = readInt(); + var blockAlign = i.readUInt16(); + var bitsPerSample = i.readUInt16(); + + if (fmtlen > 16) + i.read(fmtlen - 16); + + var nextChunk = i.readString (4); + while (nextChunk != "data") { + // read past other subchunks + i.read(readInt()); + nextChunk = i.readString (4); + } + + // data + if (nextChunk != "data") + throw "expected data subchunk"; + + var datalen = readInt(); + + var data : haxe.io.Bytes; + try { + data = i.read(datalen); + } catch (e : haxe.io.Eof) { + throw "Invalid chunk data length"; + } + + var cuePoints = new Array(); + try { + + while (true) { + var nextChunk = i.readString (4); + switch (nextChunk) { + case "cue ": + readInt(); + var nbCuePoints = readInt(); + + for (_ in 0...nbCuePoints) { + var cueId = readInt(); + readInt(); + i.readString(4); + readInt(); + readInt(); + var cueSampleOffset = readInt(); + cuePoints.push({ id : cueId, sampleOffset: cueSampleOffset }); + } + default: + var n = readInt(); + if( n < 0 ) break; + i.read(n); + } + } + + } catch (e : haxe.io.Eof) { } + + return { + header: { + format: format, + channels: channels, + samplingRate: samplingRate, + byteRate: byteRate, + blockAlign: blockAlign, + bitsPerSample: bitsPerSample + }, + data: data, + cuePoints: cuePoints + } + } + +} diff --git a/leenkx/Sources/iron/format/wav/Writer.hx b/leenkx/Sources/iron/format/wav/Writer.hx new file mode 100644 index 00000000..b97bf0d6 --- /dev/null +++ b/leenkx/Sources/iron/format/wav/Writer.hx @@ -0,0 +1,72 @@ +/* + * format - Haxe File Formats + * + * WAVE File Format + * Copyright (C) 2009 Robin Palotai + * + * Copyright (c) 2009, The Haxe Project Contributors + * 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 HAXE PROJECT 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 HAXE PROJECT 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. + */ + +package iron.format.wav; +import iron.format.wav.Data; + +class Writer { + + var o : haxe.io.Output; + + public function new(output : haxe.io.Output) { + o = output; + o.bigEndian = false; + } + + public function write(wav : WAVE) { + var hdr = wav.header; + + o.writeString("RIFF"); + writeInt(36 + wav.data.length); + o.writeString("WAVE"); + + o.writeString("fmt "); + writeInt(16); + o.writeUInt16(1); + o.writeUInt16(hdr.channels); + writeInt(hdr.samplingRate); + writeInt(hdr.byteRate); + o.writeUInt16(hdr.blockAlign); + o.writeUInt16(hdr.bitsPerSample); + + o.writeString("data"); + writeInt(wav.data.length); + o.write(wav.data); + } + + inline function writeInt( v : Int ) { + #if haxe3 + o.writeInt32(v); + #else + o.writeUInt30(v); + #end + } + +} diff --git a/leenkx/Sources/iron/object/Constraint.hx b/leenkx/Sources/iron/object/Constraint.hx index 58968c60..93763664 100644 --- a/leenkx/Sources/iron/object/Constraint.hx +++ b/leenkx/Sources/iron/object/Constraint.hx @@ -1,31 +1,119 @@ package iron.object; import iron.data.SceneFormat; +import iron.math.Vec4; +import iron.math.Quat; class Constraint { var raw: TConstraint; var target: Transform = null; - public function new(constr: TConstraint) { - raw = constr; + public function new(constraint: TConstraint) { + raw = constraint; } public function apply(transform: Transform) { if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform; + if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return; + if (raw.type == "COPY_LOCATION") { - if (raw.use_x) { - transform.world._30 = target.loc.x; - if (raw.use_offset) transform.world._30 += transform.loc.x; + if (raw.use_offset) { + if (raw.use_x) transform.world._30 += target.world._30; + if (raw.use_y) transform.world._31 += target.world._31; + if (raw.use_z) transform.world._32 += target.world._32; } - if (raw.use_y) { - transform.world._31 = target.loc.y; - if (raw.use_offset) transform.world._31 += transform.loc.y; + else { + if (raw.use_x) transform.world._30 = target.world._30; + if (raw.use_y) transform.world._31 = target.world._31; + if (raw.use_z) transform.world._32 = target.world._32; } - if (raw.use_z) { - transform.world._32 = target.loc.z; - if (raw.use_offset) transform.world._32 += transform.loc.z; + } + + else if (raw.type == "COPY_ROTATION") { + var tq = target.rot; + var mq = transform.rot; + if (raw.use_offset) { + mq.mult(tq); } + else { + if (raw.use_x) mq.x = tq.x; + if (raw.use_y) mq.y = tq.y; + if (raw.use_z) mq.z = tq.z; + mq.w = tq.w; + } + var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32); + var scale = transform.scale; + transform.world.compose(loc, mq, scale); + } + + else if (raw.type == "COPY_SCALE") { + var ts = target.scale; + if (raw.use_offset) { + if (raw.use_x) transform.scale.x *= ts.x; + if (raw.use_y) transform.scale.y *= ts.y; + if (raw.use_z) transform.scale.z *= ts.z; + } + else { + if (raw.use_x) transform.scale.x = ts.x; + if (raw.use_y) transform.scale.y = ts.y; + if (raw.use_z) transform.scale.z = ts.z; + } + var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32); + transform.world.compose(loc, transform.rot, transform.scale); + } + + else if (raw.type == "COPY_TRANSFORMS") { + transform.world.setFrom(target.world); + } + + else if (raw.type == "LIMIT_LOCATION") { + if (raw.use_min_x && transform.world._30 < raw.min_x) transform.world._30 = raw.min_x; + if (raw.use_max_x && transform.world._30 > raw.max_x) transform.world._30 = raw.max_x; + + if (raw.use_min_y && transform.world._31 < raw.min_y) transform.world._31 = raw.min_y; + if (raw.use_max_y && transform.world._31 > raw.max_y) transform.world._31 = raw.max_y; + + if (raw.use_min_z && transform.world._32 < raw.min_z) transform.world._32 = raw.min_z; + if (raw.use_max_z && transform.world._32 > raw.max_z) transform.world._32 = raw.max_z; + } + + else if (raw.type == "LIMIT_ROTATION") { + var euler = transform.rot.getEuler(); + var changed = false; + + if (raw.use_limit_x) { + if (euler.x < raw.min_x) { euler.x = raw.min_x; changed = true; } + if (euler.x > raw.max_x) { euler.x = raw.max_x; changed = true; } + } + if (raw.use_limit_y) { + if (euler.y < raw.min_y) { euler.y = raw.min_y; changed = true; } + if (euler.y > raw.max_y) { euler.y = raw.max_y; changed = true; } + } + if (raw.use_limit_z) { + if (euler.z < raw.min_z) { euler.z = raw.min_z; changed = true; } + if (euler.z > raw.max_z) { euler.z = raw.max_z; changed = true; } + } + + if (changed) { + transform.rot.fromEuler(euler.x, euler.y, euler.z); + var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32); + transform.world.compose(loc, transform.rot, transform.scale); + } + } + + else if (raw.type == "LIMIT_SCALE") { + if (raw.use_min_x && transform.scale.x < raw.min_x) transform.scale.x = raw.min_x; + if (raw.use_max_x && transform.scale.x > raw.max_x) transform.scale.x = raw.max_x; + + if (raw.use_min_y && transform.scale.y < raw.min_y) transform.scale.y = raw.min_y; + if (raw.use_max_y && transform.scale.y > raw.max_y) transform.scale.y = raw.max_y; + + if (raw.use_min_z && transform.scale.z < raw.min_z) transform.scale.z = raw.min_z; + if (raw.use_max_z && transform.scale.z > raw.max_z) transform.scale.z = raw.max_z; + + var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32); + transform.world.compose(loc, transform.rot, transform.scale); } } } diff --git a/leenkx/Sources/iron/object/CurveObject.hx b/leenkx/Sources/iron/object/CurveObject.hx new file mode 100644 index 00000000..15d495c7 --- /dev/null +++ b/leenkx/Sources/iron/object/CurveObject.hx @@ -0,0 +1,1338 @@ +package iron.object; + +import iron.Scene; +import iron.data.SceneFormat; +import iron.math.Vec4; +import iron.math.Quat; +import iron.math.Mat4; + +import iron.data.Data; +import iron.data.MeshData; +import iron.data.MaterialData; +import haxe.ds.Vector; +import kha.arrays.Float32Array; +import kha.arrays.Uint32Array; +import kha.arrays.Int16Array; +import kha.Color; +import leenkx.trait.internal.RenderDraw; + +class CurveObject extends Object { + + public var data: TCurveData; + public var splinesLength: Int; + public var equidistantSamples: Int = 0; + public var curveMesh: MeshObject = null; + + static var _p0 = new Vec4(); + static var _p1 = new Vec4(); + static var _p2 = new Vec4(); + static var _p3 = new Vec4(); + + static var _v1 = new Vec4(); + static var _v2 = new Vec4(); + + public function new(data: TCurveData) { + super(); + this.data = copyCurveData(data); + splinesLength = this.data.splines.length; + + if (this.data.shape_keys != null && this.data.shape_keys.length > 0) + applyShapeKeys(); + + if (this.data.material_refs != null && this.data.material_refs.length > 0) + addMeshObject(); + else + draw(this.data.strength, Color.fromFloats(this.data.color[0], this.data.color[1], this.data.color[2], this.data.color[3])); + } + + public function addMeshObject(){ + Data.getMesh("mesh_" + data.name, data.name, function(meshData: MeshData) { + var materials = new Vector(data.material_refs.length); + var materialsLoaded = 0; + + for (i in 0...data.material_refs.length) { + var ref = data.material_refs[i]; + + Data.getMaterial(Scene.active.raw.name, ref, function(mat: MaterialData) { + materials[i] = mat; + materialsLoaded++; + + if (materialsLoaded == data.material_refs.length) { + curveMesh = new MeshObject(meshData, materials); + curveMesh.name = this.data.object + "_mesh"; + curveMesh.raw = cast { + name: this.data.object + "_mesh", + type: "mesh_object", + }; + curveMesh.setParent(this); + curveMesh.addTrait(new leenkx.trait.internal.UniformsManager()); + } + }); + } + }); + } + + public static function copyCurveData(data: TCurveData): TCurveData { + var newData: TCurveData = { + name: data.name, + object: data.object, + splines: [], + strength: data.strength, + color: copyFloat32Array(data.color), + material_refs: data.material_refs, + shape_keys: data.shape_keys + }; + + for (spline in data.splines) { + var newSpline = { + points: [], + closed: spline.closed, + resolution: spline.resolution, + material_index: spline.material_index + }; + + for (p in spline.points) { + newSpline.points.push({ + co: copyFloat32Array(p.co), + handle_left: copyFloat32Array(p.handle_left), + handle_right: copyFloat32Array(p.handle_right) + }); + } + newData.splines.push(newSpline); + } + return newData; + } + + public static function copyFloat32Array(arr: Float32Array): Float32Array { + var newArr = new Float32Array(arr.length); + for (i in 0...arr.length) newArr[i] = arr[i]; + return newArr; + } + + public function getPoint(t: Float, splineIndex: Int = 0): Vec4 { + if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); + + if (equidistantSamples <= 0) { + return getBezierPoint(t, splineIndex); + } + else { + return getPointEquidistant(t, splineIndex, equidistantSamples); + } + } + + public function getBezierPoint(t: Float, splineIndex: Int = 0): Vec4 { + if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); + + var spline = data.splines[splineIndex]; + var points = spline.points; + if (points.length < 2) return new Vec4(points[0].co[0], points[0].co[1], points[0].co[2]); + + t = Math.max(0.0, Math.min(1.0, t)); + + if (t >= 1.0) { + var p = spline.closed ? points[0] : points[points.length - 1]; + return new Vec4(p.co[0], p.co[1], p.co[2]); + } + + var numSegments = spline.closed ? points.length : points.length - 1; + var progress = t * numSegments; + var index = Std.int(progress); + var localT = progress - index; + + var pK = points[index]; + var pK1 = (spline.closed && index == points.length - 1) ? points[0] : points[index + 1]; + + _p0.set(pK.co[0], pK.co[1], pK.co[2]); + _p1.set(pK.handle_right[0], pK.handle_right[1], pK.handle_right[2]); + _p2.set(pK1.handle_left[0], pK1.handle_left[1], pK1.handle_left[2]); + _p3.set(pK1.co[0], pK1.co[1], pK1.co[2]); + + if (spline.resolution == 12) { + return interpBezier(localT, _p0, _p1, _p2, _p3); + } else { + var stepProgress = localT * spline.resolution; + var stepIndex = Std.int(stepProgress); + var stepT = stepProgress - stepIndex; + + var tA = stepIndex / spline.resolution; + var tB = (stepIndex + 1) / spline.resolution; + + var pA = interpBezier(tA, _p0, _p1, _p2, _p3); + var pB = interpBezier(tB, _p0, _p1, _p2, _p3); + + return new Vec4( + pA.x + (pB.x - pA.x) * stepT, + pA.y + (pB.y - pA.y) * stepT, + pA.z + (pB.z - pA.z) * stepT + ); + } + } + + public function getPointEquidistant(t: Float, splineIndex: Int = 0, samples: Int = 100): Vec4 { + if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); + + var spline = data.splines[splineIndex]; + + var totalSteps = samples; + if (spline.resolution != 12) { + var numSegments = spline.closed ? spline.points.length : spline.points.length - 1; + totalSteps = spline.resolution * numSegments; + if (totalSteps < 1) totalSteps = 1; + } + + var table = [0.0]; + var totalLength = 0.0; + var lastP = getBezierPoint(0, splineIndex); + + for (i in 1...totalSteps + 1) { + var p = getBezierPoint(i / totalSteps, splineIndex); + totalLength += lastP.distanceTo(p); + table.push(totalLength); + lastP = p; + } + + var targetDistance = Math.max(0.0, Math.min(1.0, t)) * totalLength; + var low = 0; + var high = table.length - 1; + + while (low < high - 1) { + var mid = Std.int((low + high) / 2); + if (table[mid] < targetDistance) low = mid; + else high = mid; + } + + var distStart = table[low]; + var distEnd = table[high]; + var segmentLength = distEnd - distStart; + var localT = (segmentLength <= 0) ? 0 : (targetDistance - distStart) / segmentLength; + var correctedT = (low + localT) / (table.length - 1); + + return getBezierPoint(correctedT, splineIndex); + } + + function interpBezier(t: Float, p0: Vec4, p1: Vec4, p2: Vec4, p3: Vec4): Vec4 { + var it = 1.0 - t; + var b0 = it * it * it; + var b1 = 3.0 * t * it * it; + var b2 = 3.0 * t * t * it; + var b3 = t * t * t; + return new Vec4( + p0.x * b0 + p1.x * b1 + p2.x * b2 + p3.x * b3, + p0.y * b0 + p1.y * b1 + p2.y * b2 + p3.y * b3, + p0.z * b0 + p1.z * b1 + p2.z * b2 + p3.z * b3 + ); + } + + public function getTangent(t: Float, splineIndex: Int = 0): Vec4 { + var delta = 0.001; + var t1 = Math.max(0.0, t - delta); + var t2 = Math.min(1.0, t + delta); + var p1 = getPoint(t1, splineIndex); + var p2 = getPoint(t2, splineIndex); + var tangent = new Vec4(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z); + tangent.normalize(); + return tangent; + } + + public function getClosestPoint(target: Vec4, splineIndex: Int = 0): Float { + var minD = 1e10; + var bestT = 0.0; + var spline = data.splines[splineIndex]; + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var steps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); + if (steps < 1) steps = 1; + for (i in 0...steps + 1) { + var t = i / steps; + var p = getPoint(t, splineIndex); + var d = p.distanceTo(target); + if (d < minD) { + minD = d; + bestT = t; + } + } + return bestT; + } + + public function getLength(splineIndex: Int = 0): Float { + var length = 0.0; + var lastP = getPoint(0, splineIndex); + var spline = data.splines[splineIndex]; + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var steps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); + if (steps < 1) steps = 1; + for (i in 1...steps + 1) { + var p = getPoint(i / steps, splineIndex); + length += lastP.distanceTo(p); + lastP = p; + } + return length; + } + + var renderCb: kha.graphics4.Graphics->Int->Int->Void = null; + + public function draw(strength: Float = 0.005, color: kha.Color = Color.Black) { + if (renderCb != null) RenderDraw.removeOnRender(renderCb); + renderCb = RenderDraw.notifyOnRender(function(draw: RenderDraw) { + if (!visible) return; + draw.color = color; + draw.strength = strength; + + var worldMat = this.transform.world; + + for (s in 0...splinesLength) { + var spline = data.splines[s]; + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var totalSteps = spline.resolution * segments; + var step = 1.0 / (totalSteps); + for (i in 0...totalSteps) { + var t1 = i * step; + var t2 = (i + 1) * step; + + var p1 = getPoint(t1, s); + var p2 = getPoint(t2, s); + + p1.applymat4(worldMat); + p2.applymat4(worldMat); + + draw.linev(p1, p2); + } + } + }); + } + + var _q = new Quat(); + var _m = Mat4.identity(); + var _f = new Vec4(); + var _r = new Vec4(); + var _u = new Vec4(); + var _z = new Vec4(); + var _vx = new Vec4(); + var _vy = new Vec4(); + var _vz = new Vec4(); + + public function follow(obj: Object, t: Float, splineIndex: Int = 0, forwardAxis: String = "X") { + var pos = getPoint(t, splineIndex); + pos.applymat4(this.transform.world); + obj.transform.loc.setFrom(pos); + + _f.setFrom(getTangent(t, splineIndex)); + _f.applyQuat(this.transform.rot); + _f.normalize(); + + _z.set(0, 0, 1); + if (Math.abs(_f.dot(_z)) > 0.9999) { + _z.set(0, 1, 0); + } + + _r.crossvecs(_f, _z).normalize(); + _u.crossvecs(_r, _f).normalize(); + + switch (forwardAxis) { + case "X": + _vx.setFrom(_f); + _vy.set(-_r.x, -_r.y, -_r.z); + _vz.setFrom(_u); + case "-X": + _vx.set(-_f.x, -_f.y, -_f.z); + _vy.setFrom(_r); + _vz.setFrom(_u); + case "Y": + _vx.setFrom(_r); + _vy.setFrom(_f); + _vz.setFrom(_u); + case "-Y": + _vx.set(-_r.x, -_r.y, -_r.z); + _vy.set(-_f.x, -_f.y, -_f.z); + _vz.setFrom(_u); + case "Z": + _vx.setFrom(_r); + _vy.set(-_u.x, -_u.y, -_u.z); + _vz.setFrom(_f); + case "-Z": + _vx.setFrom(_r); + _vy.setFrom(_u); + _vz.set(-_f.x, -_f.y, -_f.z); + } + + _m._00 = _vx.x; _m._01 = _vx.y; _m._02 = _vx.z; + _m._10 = _vy.x; _m._11 = _vy.y; _m._12 = _vy.z; + _m._20 = _vz.x; _m._21 = _vz.y; _m._22 = _vz.z; + + _q.fromRotationMat(_m); + obj.transform.rot.setFrom(_q); + + obj.transform.buildMatrix(); + } + + override public function remove() { + visible = false; + if (renderCb != null) { + RenderDraw.removeOnRender(renderCb); + renderCb = null; + } + super.remove(); + } + + public function setShapeKey(name: String, value: Float) { + if (data.shape_keys == null) return; + for (key in data.shape_keys) { + if (key.name == name) { + key.value = value; + break; + } + } + applyShapeKeys(); + } + + public function applyShapeKeys() { + var basis = data.shape_keys[0]; + + for (s in 0...data.splines.length) { + var spline = data.splines[s]; + for (i in 0...spline.points.length) { + var pt = spline.points[i]; + var basisPt = basis.points[i]; + + pt.co[0] = basisPt.co[0]; + pt.co[1] = basisPt.co[1]; + pt.co[2] = basisPt.co[2]; + + pt.handle_left[0] = basisPt.handle_left[0]; + pt.handle_left[1] = basisPt.handle_left[1]; + pt.handle_left[2] = basisPt.handle_left[2]; + + pt.handle_right[0] = basisPt.handle_right[0]; + pt.handle_right[1] = basisPt.handle_right[1]; + pt.handle_right[2] = basisPt.handle_right[2]; + + for (k in 1...data.shape_keys.length) { + var key = data.shape_keys[k]; + var val = key.value; + if (val == 0) continue; + + var keyPt = key.points[i]; + + pt.co[0] += (keyPt.co[0] - basisPt.co[0]) * val; + pt.co[1] += (keyPt.co[1] - basisPt.co[1]) * val; + pt.co[2] += (keyPt.co[2] - basisPt.co[2]) * val; + + pt.handle_left[0] += (keyPt.handle_left[0] - basisPt.handle_left[0]) * val; + pt.handle_left[1] += (keyPt.handle_left[1] - basisPt.handle_left[1]) * val; + pt.handle_left[2] += (keyPt.handle_left[2] - basisPt.handle_left[2]) * val; + + pt.handle_right[0] += (keyPt.handle_right[0] - basisPt.handle_right[0]) * val; + pt.handle_right[1] += (keyPt.handle_right[1] - basisPt.handle_right[1]) * val; + pt.handle_right[2] += (keyPt.handle_right[2] - basisPt.handle_right[2]) * val; + } + } + } + } + + public function generateBevelMesh(bevelDepth: Float = 0.05, bevelResolution: Int = 8, factorStart: Float = 0.0, factorEnd: Float = 1.0, fillCaps: Bool = false): MeshData { + if (data.splines == null || splinesLength == 0) + return null; + + var numSides = bevelResolution <= 0 ? 4 : (bevelResolution * 2 + 4); + + var rawPositions: Array = []; + var rawNormals: Array = []; + var rawUVs: Array = []; + var indicesByMaterial = new Map>(); + + for (splineIndex in 0...splinesLength) { + var spline = data.splines[splineIndex]; + var matIdx = spline.material_index != null ? spline.material_index : 0; + if (!indicesByMaterial.exists(matIdx)) indicesByMaterial.set(matIdx, []); + var indices = indicesByMaterial.get(matIdx); + + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var totalSteps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); + if (totalSteps < 1) totalSteps = 1; + + var startStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorStart))); + var endStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorEnd))); + var isClosedLoop = spline.closed && factorStart == 0.0 && factorEnd == 1.0; + var numRings = endStep - startStep + 1; + if (numRings < 2) continue; + + var baseIndex = rawPositions.length; + + var tangent = getTangent(factorStart, splineIndex); + if (spline.closed && (factorStart == 0.0 || factorStart == 1.0)) { + var tIn = getTangent(1.0, splineIndex); + var tOut = getTangent(0.0, splineIndex); + tangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + tangent.normalize(); + } + var normal = new Vec4(0, 1, 0); + if (Math.abs(tangent.dot(normal)) > 0.9) normal.set(1, 0, 0); + var binormal = new Vec4(); + binormal.crossvecs(tangent, normal); + binormal.normalize(); + normal.crossvecs(binormal, tangent); + normal.normalize(); + + var lastTangent = tangent.clone(); + + for (s in 0...numRings) { + var stepIdx = startStep + s; + var t = stepIdx / totalSteps; + var p = getPoint(t, splineIndex); + var currTangent = getTangent(t, splineIndex); + if (spline.closed && (t == 0.0 || t == 1.0)) { + var tIn = getTangent(1.0, splineIndex); + var tOut = getTangent(0.0, splineIndex); + currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + currTangent.normalize(); + } + + var axis = new Vec4(); + axis.crossvecs(lastTangent, currTangent); + var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); + if (axis.length() > 0.00001) { + axis.normalize(); + var angle = Math.acos(dot); + var q = new Quat(); + q.fromAxisAngle(axis, angle); + normal.applyQuat(q); + binormal.applyQuat(q); + } + lastTangent = currTangent; + + for (r in 0...(numSides + 1)) { + var idx = r % numSides; + var angle = (idx / numSides) * Math.PI * 2.0; + var cosA = Math.cos(angle); + var sinA = Math.sin(angle); + + var dir = new Vec4( + normal.x * cosA + binormal.x * sinA, + normal.y * cosA + binormal.y * sinA, + normal.z * cosA + binormal.z * sinA + ); + dir.normalize(); + + var vPos = new Vec4( + p.x + dir.x * bevelDepth, + p.y + dir.y * bevelDepth, + p.z + dir.z * bevelDepth + ); + + if (isClosedLoop && s == numRings - 1) { + vPos = rawPositions[baseIndex + r].clone(); + dir = rawNormals[baseIndex + r].clone(); + } + + rawPositions.push(vPos); + rawNormals.push(dir); + rawUVs.push(new Vec4(t, r / numSides, 0)); + } + } + + var stride = numSides + 1; + for (s in 0...numRings) { + var nextS = s + 1; + if (s == numRings - 1) continue; + + for (r in 0...numSides) { + var v0 = baseIndex + s * stride + r; + var v1 = baseIndex + s * stride + r + 1; + var v2 = baseIndex + nextS * stride + r; + var v3 = baseIndex + nextS * stride + r + 1; + + indices.push(v0); + indices.push(v2); + indices.push(v1); + + indices.push(v1); + indices.push(v2); + indices.push(v3); + } + } + + var isPartial = factorStart > 0.0 || factorEnd < 1.0; + if (fillCaps && (!spline.closed || isPartial)) { + var tStart = factorStart; + var startTangent = getTangent(tStart, splineIndex); + var capStartNormal = new Vec4(-startTangent.x, -startTangent.y, -startTangent.z); + + var pStart = new Vec4(0, 0, 0); + for (r in 0...numSides) { + var pos = rawPositions[baseIndex + r]; + pStart.add(pos); + } + pStart.mult(1.0 / numSides); + + var capStartCenterIdx = rawPositions.length; + rawPositions.push(pStart); + rawNormals.push(capStartNormal); + rawUVs.push(new Vec4(0.5, 0.5, 0)); + + var capStartRingBase = rawPositions.length; + for (r in 0...numSides) { + var origIdx = baseIndex + r; + var angle = (r / numSides) * Math.PI * 2.0; + var u = 0.5 + 0.5 * Math.cos(angle); + var v = 0.5 + 0.5 * Math.sin(angle); + + rawPositions.push(rawPositions[origIdx].clone()); + rawNormals.push(capStartNormal); + rawUVs.push(new Vec4(u, v, 0)); + } + + for (r in 0...numSides) { + var nextR = (r + 1) % numSides; + var v0 = capStartRingBase + r; + var v1 = capStartRingBase + nextR; + indices.push(capStartCenterIdx); + indices.push(v1); + indices.push(v0); + } + + var tEnd = factorEnd; + var endTangent = getTangent(tEnd, splineIndex); + var capEndNormal = endTangent.clone(); + + var lastRingBase = baseIndex + (numRings - 1) * stride; + var pEnd = new Vec4(0, 0, 0); + for (r in 0...numSides) { + var pos = rawPositions[lastRingBase + r]; + pEnd.add(pos); + } + pEnd.mult(1.0 / numSides); + + var capEndCenterIdx = rawPositions.length; + rawPositions.push(pEnd); + rawNormals.push(capEndNormal); + rawUVs.push(new Vec4(0.5, 0.5, 0)); + + var capEndRingBase = rawPositions.length; + for (r in 0...numSides) { + var origIdx = lastRingBase + r; + var angle = (r / numSides) * Math.PI * 2.0; + var u = 0.5 + 0.5 * Math.cos(angle); + var v = 0.5 + 0.5 * Math.sin(angle); + + rawPositions.push(rawPositions[origIdx].clone()); + rawNormals.push(capEndNormal); + rawUVs.push(new Vec4(u, v, 0)); + } + + for (r in 0...numSides) { + var nextR = (r + 1) % numSides; + var v0 = capEndRingBase + r; + var v1 = capEndRingBase + nextR; + indices.push(capEndCenterIdx); + indices.push(v0); + indices.push(v1); + } + } + } + + if (rawPositions.length == 0) return null; + + var maxdim = 0.0; + for (p in rawPositions) { + var ax = Math.abs(p.x); + var ay = Math.abs(p.y); + var az = Math.abs(p.z); + if (ax > maxdim) maxdim = ax; + if (ay > maxdim) maxdim = ay; + if (az > maxdim) maxdim = az; + } + if (maxdim == 0) maxdim = 1.0; + maxdim *= 2; + var invdim = 1 / maxdim; + + var numVerts = rawPositions.length; + var paa = new Int16Array(numVerts * 4); + var naa = new Int16Array(numVerts * 2); + var texa = new Int16Array(numVerts * 2); + + for (i in 0...numVerts) { + var p = rawPositions[i]; + var n = rawNormals[i]; + var uv = rawUVs[i]; + + paa.set(i * 4, Std.int(p.x * 32767 * invdim)); + paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); + paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); + naa.set(i * 2, Std.int(n.x * 32767)); + naa.set(i * 2 + 1, Std.int(n.y * 32767)); + paa.set(i * 4 + 3, Std.int(n.z * 32767)); + + texa.set(i * 2, Std.int(uv.x * 32767)); + texa.set(i * 2 + 1, Std.int(uv.y * 32767)); + } + + var indexArrays: Array = []; + for (matIdx in indicesByMaterial.keys()) { + var matIndices = indicesByMaterial.get(matIdx); + var inda = new Uint32Array(matIndices.length); + for (i in 0...matIndices.length) inda.set(i, matIndices[i]); + indexArrays.push({ material: matIdx, values: inda }); + } + + var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; + var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; + var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; + + var rawmesh: TMeshData = { + name: data.name + "_mesh", + sorting_index: 0, + vertex_arrays: [pos, nor, tex], + index_arrays: indexArrays, + scale_pos: maxdim + }; + + var md = new MeshData(rawmesh, function(d: MeshData) {}); + md.geom.calculateAABB(); + return md; + } + + public function generateExtrudeMesh(width: Float = 0.1, thickness: Float = 0.1, factorStart: Float = 0.0, factorEnd: Float = 1.0, fillCaps: Bool = false): MeshData { + if (data.splines == null || splinesLength == 0) + return null; + + var rawPositions: Array = []; + var rawNormals: Array = []; + var rawUVs: Array = []; + var indicesByMaterial = new Map>(); + + var halfW = width * 0.5; + var halfH = thickness * 0.5; + + for (splineIndex in 0...splinesLength) { + var spline = data.splines[splineIndex]; + var matIdx = spline.material_index != null ? spline.material_index : 0; + if (!indicesByMaterial.exists(matIdx)) indicesByMaterial.set(matIdx, []); + var indices = indicesByMaterial.get(matIdx); + + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var totalSteps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); + if (totalSteps < 1) totalSteps = 1; + + var startStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorStart))); + var endStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorEnd))); + var isClosedLoop = spline.closed && factorStart == 0.0 && factorEnd == 1.0; + var numRings = endStep - startStep + 1; + if (numRings < 2) continue; + + var baseIndex = rawPositions.length; + + var tangent = getTangent(factorStart, splineIndex); + if (spline.closed && (factorStart == 0.0 || factorStart == 1.0)) { + var tIn = getTangent(1.0, splineIndex); + var tOut = getTangent(0.0, splineIndex); + tangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + tangent.normalize(); + } + var normal = new Vec4(0, 1, 0); + if (Math.abs(tangent.dot(normal)) > 0.9) normal.set(1, 0, 0); + var binormal = new Vec4(); + binormal.crossvecs(tangent, normal); + binormal.normalize(); + normal.crossvecs(binormal, tangent); + normal.normalize(); + + var lastTangent = tangent.clone(); + + if (thickness == 0) { + for (s in 0...numRings) { + var stepIdx = startStep + s; + var t = stepIdx / totalSteps; + var p = getPoint(t, splineIndex); + var currTangent = getTangent(t, splineIndex); + if (spline.closed && (t == 0.0 || t == 1.0)) { + var tIn = getTangent(1.0, splineIndex); + var tOut = getTangent(0.0, splineIndex); + currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + currTangent.normalize(); + } + + var axisVec = new Vec4(); + axisVec.crossvecs(lastTangent, currTangent); + var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); + if (axisVec.length() > 0.00001) { + axisVec.normalize(); + var angle = Math.acos(dot); + var q = new Quat(); + q.fromAxisAngle(axisVec, angle); + normal.applyQuat(q); + binormal.applyQuat(q); + } + lastTangent = currTangent; + + var dirW = normal; + var dirH = binormal; + + var vPos0 = new Vec4(p.x - dirW.x * halfW, p.y - dirW.y * halfW, p.z - dirW.z * halfW); + var vPos1 = new Vec4(p.x + dirW.x * halfW, p.y + dirW.y * halfW, p.z + dirW.z * halfW); + + if (isClosedLoop && s == numRings - 1) { + vPos0 = rawPositions[baseIndex].clone(); + vPos1 = rawPositions[baseIndex + 1].clone(); + } + + rawPositions.push(vPos0); + rawNormals.push(dirH); + rawUVs.push(new Vec4(t, 0.0, 0)); + + rawPositions.push(vPos1); + rawNormals.push(dirH); + rawUVs.push(new Vec4(t, 1.0, 0)); + } + + for (s in 0...numRings - 1) { + var v0 = baseIndex + s * 2; + var v1 = baseIndex + s * 2 + 1; + var v2 = baseIndex + (s + 1) * 2; + var v3 = baseIndex + (s + 1) * 2 + 1; + + indices.push(v0); + indices.push(v2); + indices.push(v1); + + indices.push(v1); + indices.push(v2); + indices.push(v3); + } + } else { + for (s in 0...numRings) { + var stepIdx = startStep + s; + var t = stepIdx / totalSteps; + var p = getPoint(t, splineIndex); + var currTangent = getTangent(t, splineIndex); + if (spline.closed && (t == 0.0 || t == 1.0)) { + var tIn = getTangent(1.0, splineIndex); + var tOut = getTangent(0.0, splineIndex); + currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + currTangent.normalize(); + } + + var axisVec = new Vec4(); + axisVec.crossvecs(lastTangent, currTangent); + var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); + if (axisVec.length() > 0.00001) { + axisVec.normalize(); + var angle = Math.acos(dot); + var q = new Quat(); + q.fromAxisAngle(axisVec, angle); + normal.applyQuat(q); + binormal.applyQuat(q); + } + lastTangent = currTangent; + + var dirW = normal; + var dirH = binormal; + + var offsets = [ + new Vec4(-dirW.x * halfW - dirH.x * halfH, -dirW.y * halfW - dirH.y * halfH, -dirW.z * halfW - dirH.z * halfH), + new Vec4( dirW.x * halfW - dirH.x * halfH, dirW.y * halfW - dirH.y * halfH, dirW.z * halfW - dirH.z * halfH), + new Vec4( dirW.x * halfW + dirH.x * halfH, dirW.y * halfW + dirH.y * halfH, dirW.z * halfW + dirH.z * halfH), + new Vec4(-dirW.x * halfW + dirH.x * halfH, -dirW.y * halfW + dirH.y * halfH, -dirW.z * halfW + dirH.z * halfH) + ]; + + var normDirs = [ + new Vec4(-dirW.x, -dirW.y, -dirW.z), + new Vec4( dirW.x, dirW.y, dirW.z), + new Vec4( dirH.x, dirH.y, dirH.z), + new Vec4(-dirH.x, -dirH.y, -dirH.z) + ]; + + for (i in 0...5) { + var idx = i % 4; + rawPositions.push(new Vec4(p.x + offsets[idx].x, p.y + offsets[idx].y, p.z + offsets[idx].z)); + rawNormals.push(normDirs[idx]); + rawUVs.push(new Vec4(t, i / 4.0, 0)); + } + } + + for (s in 0...numRings) { + var nextS = s + 1; + if (s == numRings - 1) continue; + + for (i in 0...4) { + var v0 = baseIndex + s * 5 + i; + var v1 = baseIndex + s * 5 + i + 1; + var v2 = baseIndex + nextS * 5 + i; + var v3 = baseIndex + nextS * 5 + i + 1; + + indices.push(v0); + indices.push(v2); + indices.push(v1); + + indices.push(v1); + indices.push(v2); + indices.push(v3); + } + } + + var isPartial = factorStart > 0.0 || factorEnd < 1.0; + if (fillCaps && (!spline.closed || isPartial)) { + var tStart = factorStart; + var startTangent = getTangent(tStart, splineIndex); + var capStartNormal = new Vec4(-startTangent.x, -startTangent.y, -startTangent.z); + + var pStart = new Vec4(0, 0, 0); + for (r in 0...4) { + var pos = rawPositions[baseIndex + r]; + pStart.add(pos); + } + pStart.mult(0.25); + + var capStartCenterIdx = rawPositions.length; + rawPositions.push(pStart); + rawNormals.push(capStartNormal); + rawUVs.push(new Vec4(0.5, 0.5, 0)); + + var capStartRingBase = rawPositions.length; + for (r in 0...4) { + var origIdx = baseIndex + r; + var angle = (r / 4.0) * Math.PI * 2.0; + var u = 0.5 + 0.5 * Math.cos(angle); + var v = 0.5 + 0.5 * Math.sin(angle); + + rawPositions.push(rawPositions[origIdx].clone()); + rawNormals.push(capStartNormal); + rawUVs.push(new Vec4(u, v, 0)); + } + + for (r in 0...4) { + var nextR = (r + 1) % 4; + var v0 = capStartRingBase + r; + var v1 = capStartRingBase + nextR; + indices.push(capStartCenterIdx); + indices.push(v1); + indices.push(v0); + } + + var tEnd = factorEnd; + var endTangent = getTangent(tEnd, splineIndex); + var capEndNormal = endTangent.clone(); + + var lastRingBase = baseIndex + (numRings - 1) * 5; + var pEnd = new Vec4(0, 0, 0); + for (r in 0...4) { + var pos = rawPositions[lastRingBase + r]; + pEnd.add(pos); + } + pEnd.mult(0.25); + + var capEndCenterIdx = rawPositions.length; + rawPositions.push(pEnd); + rawNormals.push(capEndNormal); + rawUVs.push(new Vec4(0.5, 0.5, 0)); + + var capEndRingBase = rawPositions.length; + for (r in 0...4) { + var origIdx = lastRingBase + r; + var angle = (r / 4.0) * Math.PI * 2.0; + var u = 0.5 + 0.5 * Math.cos(angle); + var v = 0.5 + 0.5 * Math.sin(angle); + + rawPositions.push(rawPositions[origIdx].clone()); + rawNormals.push(capEndNormal); + rawUVs.push(new Vec4(u, v, 0)); + } + + for (r in 0...4) { + var nextR = (r + 1) % 4; + var v0 = capEndRingBase + r; + var v1 = capEndRingBase + nextR; + indices.push(capEndCenterIdx); + indices.push(v0); + indices.push(v1); + } + } + } + } + + if (rawPositions.length == 0) return null; + + var maxdim = 0.0; + for (p in rawPositions) { + var ax = Math.abs(p.x); + var ay = Math.abs(p.y); + var az = Math.abs(p.z); + if (ax > maxdim) maxdim = ax; + if (ay > maxdim) maxdim = ay; + if (az > maxdim) maxdim = az; + } + if (maxdim == 0) maxdim = 1.0; + maxdim *= 2; + var invdim = 1 / maxdim; + + var numVerts = rawPositions.length; + var paa = new Int16Array(numVerts * 4); + var naa = new Int16Array(numVerts * 2); + var texa = new Int16Array(numVerts * 2); + + for (i in 0...numVerts) { + var p = rawPositions[i]; + var n = rawNormals[i]; + var uv = rawUVs[i]; + + paa.set(i * 4, Std.int(p.x * 32767 * invdim)); + paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); + paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); + naa.set(i * 2, Std.int(n.x * 32767)); + naa.set(i * 2 + 1, Std.int(n.y * 32767)); + paa.set(i * 4 + 3, Std.int(n.z * 32767)); + + texa.set(i * 2, Std.int(uv.x * 32767)); + texa.set(i * 2 + 1, Std.int(uv.y * 32767)); + } + + var indexArrays: Array = []; + for (matIdx in indicesByMaterial.keys()) { + var matIndices = indicesByMaterial.get(matIdx); + var inda = new Uint32Array(matIndices.length); + for (i in 0...matIndices.length) inda.set(i, matIndices[i]); + indexArrays.push({ material: matIdx, values: inda }); + } + + var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; + var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; + var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; + + var rawmesh: TMeshData = { + name: data.name + "_mesh", + sorting_index: 0, + vertex_arrays: [pos, nor, tex], + index_arrays: indexArrays, + scale_pos: maxdim + }; + + var md = new MeshData(rawmesh, function(d: MeshData) {}); + md.geom.calculateAABB(); + return md; + } + + public function generateDeformedMesh(baseMesh: MeshData, splineIndex = -1, forwardAxis: String = "X", repetitions: Int, factorStart: Float = 0.0, factorEnd: Float = 1.0): MeshData { + if (data.splines == null || splinesLength == 0 || baseMesh == null || repetitions <= 0) + return null; + + var rawPositions: Array = []; + var rawNormals: Array = []; + var rawUVs: Array = []; + var indicesByMaterial = new Map>(); + + var baseRaw = baseMesh.raw; + var basePos: Int16Array = null; + var baseNor: Int16Array = null; + var baseTex: Int16Array = null; + + for (va in baseRaw.vertex_arrays) { + if (va.attrib == "pos") basePos = va.values; + else if (va.attrib == "nor") baseNor = va.values; + else if (va.attrib == "tex") baseTex = va.values; + } + + if (basePos == null) return null; + + var baseVertCount = Std.int(basePos.length / 4); + var baseScale = baseRaw.scale_pos; + + var minVal = 1e10; + var maxVal = -1e10; + var unpackedPos = new Array(); + var unpackedNor = new Array(); + var unpackedTex = new Array(); + + for (i in 0...baseVertCount) { + var px = (basePos.get(i * 4) / 32767) * baseScale; + var py = (basePos.get(i * 4 + 1) / 32767) * baseScale; + var pz = (basePos.get(i * 4 + 2) / 32767) * baseScale; + unpackedPos.push(new Vec4(px, py, pz)); + + var fval = 0.0; + switch (forwardAxis) { + case "X": fval = px; + case "-X": fval = -px; + case "Y": fval = py; + case "-Y": fval = -py; + case "Z": fval = pz; + case "-Z": fval = -pz; + default: fval = px; + } + + if (fval < minVal) minVal = fval; + if (fval > maxVal) maxVal = fval; + + if (baseNor != null) { + var nx = baseNor.get(i * 2) / 32767; + var ny = baseNor.get(i * 2 + 1) / 32767; + var nz = basePos.get(i * 4 + 3) / 32767; + unpackedNor.push(new Vec4(nx, ny, nz)); + } else { + unpackedNor.push(new Vec4(0, 0, 1)); + } + + if (baseTex != null) { + var tu = baseTex.get(i * 2) / 32767; + var tv = baseTex.get(i * 2 + 1) / 32767; + unpackedTex.push(new Vec4(tu, tv, 0)); + } else { + unpackedTex.push(new Vec4(0, 0, 0)); + } + } + + var sizeVal = maxVal - minVal; + if (sizeVal <= 0.00001) sizeVal = 1.0; + + var startIdx = splineIndex == -1 ? 0 : splineIndex; + var endIdx = splineIndex == -1 ? splinesLength : splineIndex + 1; + + for (sIdx in startIdx...endIdx) { + var spline = data.splines[sIdx]; + var segments = spline.closed ? spline.points.length : spline.points.length - 1; + var frameCount = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); + var framesNor = new Array(); + var framesBin = new Array(); + var framesTan = new Array(); + + var startTangent = getTangent(0.0, sIdx); + if (spline.closed) { + var tIn = getTangent(1.0, sIdx); + var tOut = getTangent(0.0, sIdx); + startTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + startTangent.normalize(); + } + + var _z = new Vec4(0, 0, 1); + if (Math.abs(startTangent.dot(_z)) > 0.9999) { + _z.set(0, 1, 0); + } + var _r = new Vec4(); + _r.crossvecs(startTangent, _z).normalize(); + var _u = new Vec4(); + _u.crossvecs(_r, startTangent).normalize(); + + var normal = new Vec4(-_r.x, -_r.y, -_r.z); + var binormal = new Vec4(_u.x, _u.y, _u.z); + + var lastTangent = startTangent.clone(); + + for (i in 0...frameCount + 1) { + var t = i / frameCount; + var currTangent = getTangent(t, sIdx); + + if (spline.closed && (t == 0.0 || t == 1.0)) { + var tIn = getTangent(1.0, sIdx); + var tOut = getTangent(0.0, sIdx); + currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); + currTangent.normalize(); + } + + var axis = new Vec4(); + axis.crossvecs(lastTangent, currTangent); + var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); + if (axis.length() > 0.00001) { + axis.normalize(); + var angle = Math.acos(dot); + var q = new Quat(); + q.fromAxisAngle(axis, angle); + normal.applyQuat(q); + binormal.applyQuat(q); + } + lastTangent = currTangent.clone(); + + framesNor.push(normal.clone()); + framesBin.push(binormal.clone()); + framesTan.push(currTangent.clone()); + } + + if (spline.closed) { + var endDot = Math.max(-1.0, Math.min(1.0, framesNor[0].dot(framesNor[frameCount]))); + var endAngle = Math.acos(endDot); + + var crossTest = new Vec4(); + crossTest.crossvecs(framesNor[0], framesNor[frameCount]); + if (framesTan[0].dot(crossTest) < 0) { + endAngle = -endAngle; + } + + for (i in 0...frameCount + 1) { + var t = i / frameCount; + var qFix = new Quat(); + qFix.fromAxisAngle(framesTan[i], -endAngle * t); + framesNor[i].applyQuat(qFix); + framesBin[i].applyQuat(qFix); + } + } + + for (rep in 0...repetitions) { + var baseIndex = rawPositions.length; + var repTStart = factorStart + (rep / repetitions) * (factorEnd - factorStart); + var repTEnd = factorStart + ((rep + 1) / repetitions) * (factorEnd - factorStart); + + for (i in 0...baseVertCount) { + var p = unpackedPos[i]; + var n = unpackedNor[i]; + var uv = unpackedTex[i]; + + var fval = 0.0; + var rval = 0.0; + var uval = 0.0; + var nfval = 0.0; + var nrval = 0.0; + var nuval = 0.0; + + switch (forwardAxis) { + case "X": + fval = p.x; rval = p.y; uval = p.z; + nfval = n.x; nrval = n.y; nuval = n.z; + case "-X": + fval = -p.x; rval = -p.y; uval = p.z; + nfval = -n.x; nrval = -n.y; nuval = n.z; + case "Y": + fval = p.y; rval = -p.x; uval = p.z; + nfval = n.y; nrval = -n.x; nuval = n.z; + case "-Y": + fval = -p.y; rval = p.x; uval = p.z; + nfval = -n.y; nrval = n.x; nuval = n.z; + case "Z": + fval = p.z; rval = -p.x; uval = -p.y; + nfval = n.z; nrval = -n.x; nuval = -n.y; + case "-Z": + fval = -p.z; rval = -p.x; uval = p.y; + nfval = -n.z; nrval = -n.x; nuval = n.y; + default: + fval = p.x; rval = p.y; uval = p.z; + nfval = n.x; nrval = n.y; nuval = n.z; + } + + var localT = (fval - minVal) / sizeVal; + var curveT = repTStart + localT * (repTEnd - repTStart); + + if (curveT < 0) curveT = 0; + if (curveT > 1) curveT = 1; + + var fIdx = curveT * frameCount; + var i0 = Std.int(fIdx); + var i1 = i0 + 1; + if (i1 > frameCount) i1 = frameCount; + var blend = fIdx - i0; + + var n0 = framesNor[i0]; + var n1 = framesNor[i1]; + var b0 = framesBin[i0]; + var b1 = framesBin[i1]; + var t0 = framesTan[i0]; + var t1 = framesTan[i1]; + + var normalVec = new Vec4( + n0.x + (n1.x - n0.x) * blend, + n0.y + (n1.y - n0.y) * blend, + n0.z + (n1.z - n0.z) * blend + ); + normalVec.normalize(); + + var binormalVec = new Vec4( + b0.x + (b1.x - b0.x) * blend, + b0.y + (b1.y - b0.y) * blend, + b0.z + (b1.z - b0.z) * blend + ); + binormalVec.normalize(); + + var tangentVec = new Vec4( + t0.x + (t1.x - t0.x) * blend, + t0.y + (t1.y - t0.y) * blend, + t0.z + (t1.z - t0.z) * blend + ); + tangentVec.normalize(); + + var curvePos = getPoint(curveT, sIdx); + + var finalPos = new Vec4( + curvePos.x + normalVec.x * rval + binormalVec.x * uval, + curvePos.y + normalVec.y * rval + binormalVec.y * uval, + curvePos.z + normalVec.z * rval + binormalVec.z * uval + ); + + var finalNor = new Vec4( + normalVec.x * nrval + binormalVec.x * nuval + tangentVec.x * nfval, + normalVec.y * nrval + binormalVec.y * nuval + tangentVec.y * nfval, + normalVec.z * nrval + binormalVec.z * nuval + tangentVec.z * nfval + ); + finalNor.normalize(); + + rawPositions.push(finalPos); + rawNormals.push(finalNor); + rawUVs.push(new Vec4(uv.x, uv.y, 0)); + } + + for (ia in baseRaw.index_arrays) { + var mat = ia.material; + if (!indicesByMaterial.exists(mat)) indicesByMaterial.set(mat, []); + var destIndices = indicesByMaterial.get(mat); + var srcIndices = ia.values; + + for (i in 0...srcIndices.length) { + destIndices.push(baseIndex + srcIndices.get(i)); + } + } + } + } + + var maxdim = 0.0; + for (p in rawPositions) { + var ax = Math.abs(p.x); + var ay = Math.abs(p.y); + var az = Math.abs(p.z); + if (ax > maxdim) maxdim = ax; + if (ay > maxdim) maxdim = ay; + if (az > maxdim) maxdim = az; + } + if (maxdim == 0) maxdim = 1.0; + maxdim *= 2; + var invdim = 1 / maxdim; + + var numVerts = rawPositions.length; + var paa = new Int16Array(numVerts * 4); + var naa = new Int16Array(numVerts * 2); + var texa = new Int16Array(numVerts * 2); + + for (i in 0...numVerts) { + var p = rawPositions[i]; + var n = rawNormals[i]; + var uv = rawUVs[i]; + + paa.set(i * 4, Std.int(p.x * 32767 * invdim)); + paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); + paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); + naa.set(i * 2, Std.int(n.x * 32767)); + naa.set(i * 2 + 1, Std.int(n.y * 32767)); + paa.set(i * 4 + 3, Std.int(n.z * 32767)); + + texa.set(i * 2, Std.int(uv.x * 32767)); + texa.set(i * 2 + 1, Std.int(uv.y * 32767)); + } + + var indexArrays: Array = []; + for (matIdx in indicesByMaterial.keys()) { + var matIndices = indicesByMaterial.get(matIdx); + var inda = new Uint32Array(matIndices.length); + for (i in 0...matIndices.length) inda.set(i, matIndices[i]); + indexArrays.push({ material: matIdx, values: inda }); + } + + var posVA: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; + var norVA: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; + var texVA: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; + + var rawmesh: TMeshData = { + name: data.name + "_mesh", + sorting_index: 0, + vertex_arrays: [posVA, norVA, texVA], + index_arrays: indexArrays, + scale_pos: maxdim + }; + + var md = new MeshData(rawmesh, function(d: MeshData) {}); + md.geom.calculateAABB(); + return md; + } + +} diff --git a/leenkx/Sources/iron/object/LightObject.hx b/leenkx/Sources/iron/object/LightObject.hx index b90c2571..3c25324d 100644 --- a/leenkx/Sources/iron/object/LightObject.hx +++ b/leenkx/Sources/iron/object/LightObject.hx @@ -11,6 +11,12 @@ import iron.object.CameraObject; class LightObject extends Object { public var data: LightData; + public var color: Vec4; + public var strength: Float; + #if lnx_spot + public var size: Float; + public var blend: Float; + #end #if rp_shadowmap #if lnx_shadowmap_atlas @@ -79,6 +85,15 @@ class LightObject extends Object { super(); this.data = data; + this.color = new Vec4(data.raw.color[0], data.raw.color[1], data.raw.color[2]); + this.strength = data.raw.strength; + + #if lnx_spot + if (data.raw.type == "spot"){ + this.size = data.raw.spot_size; + this.blend = data.raw.spot_blend; + } + #end var type = data.raw.type; var fov = data.raw.fov; @@ -374,7 +389,7 @@ class LightObject extends Object { // Centralize discarding conditions when iterating over lights // Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray) public inline static function discardLight(light: LightObject) { - return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun"; + return !light.visible || light.strength == 0.0 || light.data.raw.type == "sun"; } // Discarding conditions but with culling included public inline static function discardLightCulled(light: LightObject) { @@ -457,7 +472,7 @@ class LightObject extends Object { lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz()); lpos.applymat4(camera.V); lpos.z *= -1.0; - var radius = getRadius(l.data.raw.strength); + var radius = getRadius(l.strength); var minX = 0; var minY = 0; var minZ = 0; @@ -552,10 +567,10 @@ class LightObject extends Object { lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x // light color - var f = l.data.raw.strength; - lightsArray[i * 12 + 4] = l.data.raw.color[0] * f; - lightsArray[i * 12 + 5] = l.data.raw.color[1] * f; - lightsArray[i * 12 + 6] = l.data.raw.color[2] * f; + var f = l.strength; + lightsArray[i * 12 + 4] = l.color.x * f; + lightsArray[i * 12 + 5] = l.color.y * f; + lightsArray[i * 12 + 6] = l.color.z * f; lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y // other data @@ -566,13 +581,13 @@ class LightObject extends Object { #if lnx_spot if (l.data.raw.type == "spot") { - lightsArray[i * 12 + 9] = l.data.raw.spot_size; + lightsArray[i * 12 + 9] = l.size; var dir = l.look().normalize(); lightsArraySpot[i * 8 ] = dir.x; lightsArraySpot[i * 8 + 1] = dir.y; lightsArraySpot[i * 8 + 2] = dir.z; - lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend; + lightsArraySpot[i * 8 + 3] = l.blend; // Premultiply scale with z component var scale = l.transform.scale; diff --git a/leenkx/Sources/iron/object/MeshObject.hx b/leenkx/Sources/iron/object/MeshObject.hx index 472abea3..8a5a3435 100644 --- a/leenkx/Sources/iron/object/MeshObject.hx +++ b/leenkx/Sources/iron/object/MeshObject.hx @@ -28,13 +28,15 @@ class MeshObject extends Object { public var cameraList: Array = null; public var screenSize = 0.0; public var frustumCulling = true; - public var tilesheet: Tilesheet = null; + public var activeTilesheet: Tilesheet = null; + public var tilesheets: Array = null; public var skip_context: String = null; // Do not draw this context public var force_context: String = null; // Draw only this context static var lastPipeline: PipelineState = null; #if lnx_morph_target public var morphTarget: MorphTarget = null; #end + public var vertexGroups: Map> = null; #if lnx_veloc public var prevMatrix = Mat4.identity(); @@ -50,6 +52,10 @@ class MeshObject extends Object { public function setData(data: MeshData) { this.data = data; + + if (this.materials != null && this.materials.length > 0) + data.geom.instanceElements = @:privateAccess this.materials[0].shader.contexts[0].instanceElements; + data.refcount++; #if (!lnx_batch) @@ -87,7 +93,8 @@ class MeshObject extends Object { particleSystems = null; } #end - if (tilesheet != null) tilesheet.remove(); + if (activeTilesheet != null) activeTilesheet.remove(); + if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; } if (Scene.active != null) Scene.active.meshes.remove(this); #if (rp_renderer == "Deferred") if (Scene.active != null) Scene.active.markMaterialParamsDirty(); @@ -126,12 +133,34 @@ class MeshObject extends Object { #end public function setupTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData) { - tilesheet = new Tilesheet(tilesheetData, this); + activeTilesheet = new Tilesheet(tilesheetData, this); + if (tilesheets == null) tilesheets = new Array(); + tilesheets.push(activeTilesheet); + } + + public function setActiveTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData, tilesheetActionRef: String = null) { + var set = false; + if (tilesheets != null) { + for (ts in tilesheets) { + if (ts.raw == tilesheetData) { + if (activeTilesheet != null) activeTilesheet.pause(); + activeTilesheet = ts; + if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef); + set = true; + break; + } + } + } + if (!set) { + if (activeTilesheet != null) activeTilesheet.pause(); + setupTilesheet(tilesheetData); + if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef); + } } public function setTilesheetAction(actionRef: String) { - if (tilesheet != null) { - tilesheet.play(actionRef); + if (activeTilesheet != null) { + activeTilesheet.play(actionRef); } } @@ -164,6 +193,7 @@ class MeshObject extends Object { } function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool { + var isShadow = context == "shadowmap"; if (camera == null) return false; if (camera.data.raw.frustum_culling && frustumCulling) { @@ -172,11 +202,12 @@ class MeshObject extends Object { var radiusScale = data.isSkinned ? 2.0 : 1.0; #if lnx_gpu_particles // particleSystems for update, particleOwner for render - if (particleSystems != null || particleOwner != null) radiusScale *= 1000; + if (particleSystems != null && particleSystems.length > 0) return setCulled(isShadow, false); #end + /* if (context == "voxel") radiusScale *= 100; - if (data.geom.instanced) radiusScale *= 100; - var isShadow = context == "shadowmap"; + if (data.geom.instanced) radiusScale *= 100;*/ + if (data.geom.instanced) return setCulled(isShadow, false); var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes; if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum @@ -191,8 +222,9 @@ class MeshObject extends Object { } } - culled = false; - return culled; + //culled = false; + //return culled; + return setCulled(isShadow, false); } function skipContext(context: String, mat: MaterialData): Bool { @@ -227,11 +259,6 @@ class MeshObject extends Object { if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return; var meshContext = raw != null ? context == "mesh" : false; - // Update tilesheet - if (tilesheet != null && meshContext) { - tilesheet.update(); - } - if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return; #if lnx_gpu_particles diff --git a/leenkx/Sources/iron/object/MorphTarget.hx b/leenkx/Sources/iron/object/MorphTarget.hx index 71edd367..cfd4fbb8 100644 --- a/leenkx/Sources/iron/object/MorphTarget.hx +++ b/leenkx/Sources/iron/object/MorphTarget.hx @@ -61,6 +61,25 @@ class MorphTarget { public inline function setMorphValueDirect(index: Int, value: Float) { morphWeights.set(index, value); } + + public function getMorphValue(name: String): Float { + var i = morphMap.get(name); + return (i != null) ? morphWeights.get(i) : 0.0; + } + + public function hasMorph(name: String): Bool { + return morphMap.exists(name); + } + + public function resetWeights() { + for (i in 0...morphWeights.length) { + morphWeights.set(i, 0.0); + } + } + + public function getMorphNames(): Array { + return [for (key in morphMap.keys()) key]; + } } #end diff --git a/leenkx/Sources/iron/object/Object.hx b/leenkx/Sources/iron/object/Object.hx index 6eca6f96..345aeec5 100644 --- a/leenkx/Sources/iron/object/Object.hx +++ b/leenkx/Sources/iron/object/Object.hx @@ -27,7 +27,6 @@ class Object { public var culled = false; // Object was culled last frame public var culledMesh = false; public var culledShadow = false; - public var vertex_groups: Map> = null; public var properties: Map = null; var isEmpty = false; @@ -95,6 +94,7 @@ class Object { Removes the game object from the scene. **/ public function remove() { + Scene.active.removeFromGroups(this); if (isEmpty && Scene.active != null) Scene.active.empties.remove(this); if (animation != null) animation.remove(); while (children.length > 0) children[0].remove(); @@ -111,16 +111,12 @@ class Object { @return Object or null **/ public function getChild(name: String): Object { - if (this.name == name) return this; - else if (this.filename != "") { - if (this.name == name + "_" + this.filename) return this; - } - for (c in children) { + if (c.name == name) return c; + if (c.filename != "" && c.name == name + "_" + c.filename) return c; var r = c.getChild(name); if (r != null) return r; } - return null; } @@ -145,12 +141,10 @@ class Object { } public function getChildOfType(type: Class): T { - if (Std.isOfType(this, type)) return cast this; - else { - for (c in children) { - var r = c.getChildOfType(type); - if (r != null) return r; - } + for (c in children) { + if (Std.isOfType(c, type)) return cast c; + var r = c.getChildOfType(type); + if (r != null) return r; } return null; } diff --git a/leenkx/Sources/iron/object/ParticleSystemCPU.hx b/leenkx/Sources/iron/object/ParticleSystemCPU.hx index 3e0467e5..9b3826bc 100644 --- a/leenkx/Sources/iron/object/ParticleSystemCPU.hx +++ b/leenkx/Sources/iron/object/ParticleSystemCPU.hx @@ -8,6 +8,7 @@ import iron.data.SceneFormat; import iron.math.Quat; import iron.math.Vec3; import iron.math.Vec4; +import iron.object.CurveObject; import iron.object.MeshObject; import iron.object.Object; import iron.system.Time; @@ -19,6 +20,10 @@ import kha.arrays.Uint32Array; class ParticleSystemCPU { public var data: ParticleData; public var speed: FastFloat = 1.0; // Not used yet. Added to go in hand with `ParticleSystemGPU` + public var curveGuides: Array = []; + public var curveGuideStrength: FastFloat = 1.0; + public var curveGuideSpeed: FastFloat = 1.0; + var paused: Bool = false; var r: TParticleData; // Format @@ -37,6 +42,7 @@ class ParticleSystemCPU { // Velocity var velocity: Vec3 = new Vec3(0.0, 0.0, 1.0); // object_align_factor: Float32Array var velocityRandom: FastFloat = 0.0; // factor_random + var normalFactor: FastFloat = 0.0; // Rotation var rotation: Bool = false; // use_rotations @@ -114,11 +120,12 @@ class ParticleSystemCPU { scale = r.particle_size; scaleRandom = r.size_random; - velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate).mult(1 / scale); + velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate); velocityRandom = r.factor_random * (frameRate / baseFrameRate); + normalFactor = r.normal_factor * (frameRate / baseFrameRate); if (Scene.active.raw.gravity != null) { - gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate).mult(1 / scale); + gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate); } gravityFactor = r.weight_gravity * (frameRate / baseFrameRate); textureFactor = r.weight_texture; @@ -139,39 +146,36 @@ class ParticleSystemCPU { scaleElementsCount = getRampElementsLength(); scaleRampSizeFactor = getRampSizeFactor(); - switch (type) { - case 0: // Emission - loopAnim = { - tick: function () { - spawnTime += Time.delta * Time.scale; - var expected: Int = Math.floor(spawnTime / spawnRate); - while (spawnedParticles < expected && spawnedParticles < count) { - spawnParticle(); - spawnedParticles++; - } - updateParticles(); - }, - target: null, - props: null, - duration: loop ? lifetimeSeconds : lifetimeSeconds * 2, - done: function () { - if (loop) start(); - } - } - - Scene.active.notifyOnInit(function () { - if (autoStart) start(); - }); - case 1: // Hair - Scene.active.notifyOnInit(function () { - for (i in 0...count) spawnParticle(); - }); - default: - } - Scene.active.notifyOnInit(function () { for (i in 0...count) addToPool(); + + switch (type) { + case 0: // Emission + loopAnim = { + tick: function () { + if (paused) return; + spawnTime += Time.delta; + var expected: Int = Math.floor(spawnTime / spawnRate); + while (spawnedParticles < expected && spawnedParticles < count) { + spawnParticle(); + spawnedParticles++; + } + updateParticles(); + }, + target: null, + props: null, + duration: loop ? lifetimeSeconds : lifetimeSeconds * 2, + done: function () { + if (loop) start(); + } + } + if (autoStart) start(); + case 1: // Hair + for (i in 0...count) spawnParticle(); + default: + } }); + }); } @@ -182,14 +186,12 @@ class ParticleSystemCPU { Tween.to(loopAnim); } - // TODO public function pause() { - + paused = true; } - // TODO public function resume() { - + paused = false; } public function stop() { @@ -246,6 +248,8 @@ class ParticleSystemCPU { var scalePos: FastFloat = owner.data.scalePos; var scalePosParticle: FastFloat = cast(o, MeshObject).data.scalePos; + var normDir: Vec3 = new Vec3(); + // TODO: add all properties from Blender's UI switch (emitFrom) { case 0: // Vertices @@ -253,6 +257,8 @@ class ParticleSystemCPU { var i: Int = Std.int(Math.random() * (pa.values.length / pa.size)); var loc: Vec4 = new Vec4(pa.values[i * pa.size] * normFactor, pa.values[i * pa.size + 1] * normFactor, pa.values[i * pa.size + 2] * normFactor, 1); + if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize(); + if (!localCoords) { loc.applyQuat(objectRot); loc.add(objectPos); @@ -274,6 +280,9 @@ class ParticleSystemCPU { var pos: Vec3 = randomPointInTriangle(v0, v1, v2); var loc: Vec4 = new Vec4(pos.x, pos.y, pos.z, 1).mult(normFactor); + + if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize(); + if (!localCoords) { loc.applyQuat(objectRot); loc.add(objectPos); @@ -285,6 +294,8 @@ class ParticleSystemCPU { scaleFactorVolume.mult(0.5); var loc: Vec4 = new Vec4((Math.random() * 2.0 - 1.0) * scaleFactorVolume.x, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z, 1); + if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize(); + if (!localCoords) { loc.applyQuat(objectRot); loc.add(objectPos); @@ -310,7 +321,9 @@ class ParticleSystemCPU { var randomZ: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom; var g: Vec3 = new Vec3(); - var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX, velocity.y + randomY, velocity.z + randomZ, 1); + if (normalFactor != 0.0) normDir = normDir.mult(normalFactor); + + var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX + normDir.x, velocity.y + randomY + normDir.y, velocity.z + randomZ + normDir.z, 1); if (!localCoords) rotatedVelocity.applyQuat(objectRot); if (rotation) { @@ -371,7 +384,7 @@ class ParticleSystemCPU { function updateParticles() { for (particle => physics in particlePhysics) { - physics.age += Time.delta * Time.scale; + physics.age += Time.delta; if (physics.age >= physics.lifetime) { particlePhysics.remove(particle); @@ -379,14 +392,63 @@ class ParticleSystemCPU { continue; } - physics.velocity.x += physics.gravity.x * Time.delta * Time.scale; - physics.velocity.y += physics.gravity.y * Time.delta * Time.scale; - physics.velocity.z += physics.gravity.z * Time.delta * Time.scale; + physics.velocity.x += physics.gravity.x * Time.delta; + physics.velocity.y += physics.gravity.y * Time.delta; + physics.velocity.z += physics.gravity.z * Time.delta; + + if (curveGuides != null && curveGuides.length > 0) { + var curveVelX: FastFloat = 0.0; + var curveVelY: FastFloat = 0.0; + var curveVelZ: FastFloat = 0.0; + var validCurves: Int = 0; + + for (curve in curveGuides) { + if (curve != null && curve.data != null && curve.data.splines != null && curve.splinesLength > 0) { + var t = physics.age / physics.lifetime; + + var tangent = curve.getTangent(t, 0); + tangent.w = 0.0; + tangent.applymat4(curve.transform.world); + tangent.normalize(); + + var curveLen = curve.getLength(0); + var speed = (curveLen / physics.lifetime) * curveGuideSpeed; + + var tgtX = tangent.x * speed; + var tgtY = tangent.y * speed; + var tgtZ = tangent.z * speed; + + if (localCoords) { + var targetVel = new Vec4(tgtX, tgtY, tgtZ, 0.0); + var invOwnerRot = new Quat(-owner.transform.rot.x, -owner.transform.rot.y, -owner.transform.rot.z, owner.transform.rot.w); + targetVel.applyQuat(invOwnerRot); + tgtX = targetVel.x; + tgtY = targetVel.y; + tgtZ = targetVel.z; + } + + curveVelX += tgtX; + curveVelY += tgtY; + curveVelZ += tgtZ; + validCurves++; + } + } + + if (validCurves > 0) { + curveVelX /= validCurves; + curveVelY /= validCurves; + curveVelZ /= validCurves; + + physics.velocity.x += (curveVelX - physics.velocity.x) * curveGuideStrength; + physics.velocity.y += (curveVelY - physics.velocity.y) * curveGuideStrength; + physics.velocity.z += (curveVelZ - physics.velocity.z) * curveGuideStrength; + } + } particle.transform.translate( - physics.velocity.x * Time.delta * Time.scale, - physics.velocity.y * Time.delta * Time.scale, - physics.velocity.z * Time.delta * Time.scale + physics.velocity.x * Time.delta, + physics.velocity.y * Time.delta, + physics.velocity.z * Time.delta ); if (rotation && dynamicRotation && orientationAxis == 3) setVelocityHair(particle, physics.velocity, randQuat, phaseQuat); diff --git a/leenkx/Sources/iron/object/ParticleSystemGPU.hx b/leenkx/Sources/iron/object/ParticleSystemGPU.hx index 94b9b66e..fe2ae568 100644 --- a/leenkx/Sources/iron/object/ParticleSystemGPU.hx +++ b/leenkx/Sources/iron/object/ParticleSystemGPU.hx @@ -141,10 +141,10 @@ class ParticleSystemGPU { dimx = object.transform.dim.x; dimy = object.transform.dim.y; - if (object.tilesheet != null) { - tilesx = object.tilesheet.getTilesX(); - tilesy = object.tilesheet.getTilesY(); - tilesFramerate = object.tilesheet.action.framerate; + if (object.activeTilesheet != null) { + tilesx = object.activeTilesheet.getTilesX(); + tilesy = object.activeTilesheet.getTilesY(); + tilesFramerate = object.activeTilesheet.action.framerate; } // Animate diff --git a/leenkx/Sources/iron/object/SpeakerObject.hx b/leenkx/Sources/iron/object/SpeakerObject.hx index 1c1a7f75..7fd961a3 100644 --- a/leenkx/Sources/iron/object/SpeakerObject.hx +++ b/leenkx/Sources/iron/object/SpeakerObject.hx @@ -1,6 +1,7 @@ package iron.object; import kha.FastFloat; +import kha.Sound; import kha.audio1.AudioChannel; import iron.data.Data; import iron.data.SceneFormat; @@ -13,9 +14,10 @@ class SpeakerObject extends Object { public var data: TSpeakerData; public var paused(default, null) = false; - public var sound(default, null): kha.Sound = null; + public var sound(default, null): Sound = null; public var channels(default, null): Array = []; public var volume(default, null) : FastFloat; + public var sampleRate(default, null) : Int; public function new(data: TSpeakerData) { super(); @@ -26,28 +28,32 @@ class SpeakerObject extends Object { if (data.sound == "") return; - Data.getSound(data.sound, function(sound: kha.Sound) { - this.sound = sound; + Data.getSound(data.sound, function(sound: Sound) { + this.sound = cloneSound(sound); App.notifyOnInit(init); }); } function init() { + sampleRate = sound.sampleRate; + if (data.pitch != 1.0) + sound.sampleRate = Std.int(sampleRate * data.pitch); if (visible && data.play_on_start) play(); } - public function play() { - if (sound == null || data.muted) return; + public function play(): AudioChannel { + if (sound == null || data.muted) return null; if (paused) { for (c in channels) c.play(); paused = false; - return; + return null; } var channel = Audio.play(sound, data.loop, data.stream); if (channel != null) { channels.push(channel); if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update); } + return channel; } public function pause() { @@ -65,14 +71,24 @@ class SpeakerObject extends Object { data.sound = sound; - Data.getSound(sound, function(sound: kha.Sound) { - this.sound = sound; + Data.getSound(sound, function(sound: Sound) { + this.sound = cloneSound(sound); }); + + sampleRate = this.sound.sampleRate; + if (data.pitch != 1.0) + this.sound.sampleRate = Std.int(sampleRate * data.pitch); } public function setVolume(volume: FastFloat) { data.volume = volume; } + + public function setPosition(position: Float) { + for (c in channels) + if (position < c.length) c.position = position; + } + function update() { if (paused) return; for (c in channels) if (c.finished) channels.remove(c); @@ -103,6 +119,17 @@ class SpeakerObject extends Object { super.remove(); } + function cloneSound(sound: Sound): Sound { + if (sound == null) return null; + var s = Type.createEmptyInstance(Sound); + s.compressedData = sound.compressedData; + s.uncompressedData = sound.uncompressedData; + s.sampleRate = sound.sampleRate; + s.length = sound.length; + s.channels = sound.channels; + return s; + } + #end } diff --git a/leenkx/Sources/iron/object/Tilesheet.hx b/leenkx/Sources/iron/object/Tilesheet.hx index d3ab97d7..561214df 100644 --- a/leenkx/Sources/iron/object/Tilesheet.hx +++ b/leenkx/Sources/iron/object/Tilesheet.hx @@ -1,11 +1,12 @@ package iron.object; import iron.App; +import iron.Scene; import iron.data.SceneFormat; import iron.system.Time; import haxe.ds.Map; - +@:allow(iron.Scene) class Tilesheet { public var tileX: Float = 0.0; @@ -14,7 +15,8 @@ class Tilesheet { public var flipY: Bool = false; public var paused: Bool = false; public var frame: Int = 0; - public var actions: Array; + public var raw: TTilesheetData = null; + public var actions: Array = null; public var action: TTilesheetAction = null; public var ready: Bool = false; @@ -31,8 +33,11 @@ class Tilesheet { public function new(tilesheetData: TTilesheetData, ownerObject: MeshObject = null) { owner = ownerObject; + raw = tilesheetData; actions = tilesheetData.actions; + Scene.active.tilesheets.push(this); + pendingAction = tilesheetData.start_action; if ((pendingAction == null || pendingAction == "") && actions.length > 0) { pendingAction = actions[0].name; @@ -262,8 +267,10 @@ class Tilesheet { } public function remove() { + Scene.active.tilesheets.remove(this); ready = false; action = null; + raw = null; actions = null; owner = null; currentMesh = null; diff --git a/leenkx/Sources/iron/object/Transform.hx b/leenkx/Sources/iron/object/Transform.hx index ce155f61..d4a7a1b2 100644 --- a/leenkx/Sources/iron/object/Transform.hx +++ b/leenkx/Sources/iron/object/Transform.hx @@ -106,6 +106,7 @@ class Transform { Rebuild the matrices, if needed. **/ public function update() { + if (object.constraints != null && object.constraints.length > 0) dirty = true; if (dirty) buildMatrix(); } @@ -150,12 +151,19 @@ class Transform { if (boneParent != null) local.multmats(boneParent, local); if (object.parent != null && !localOnly) { - world.multmats3x4(local, object.parent.transform.world); + // Swap multiplication order for linked objects to keep local transform intact + var swapMult: Bool = object.raw != null && object.parent.raw != null && object.parent.raw.group_ref != null && object.parent.raw.group_ref != ""; + var a: Mat4 = swapMult ? object.parent.transform.world : local; + var b: Mat4 = swapMult ? local : object.parent.transform.world; + world.multmats3x4(a, b); } else { world.setFrom(local); } + // Constraints + if (object.constraints != null) for (c in object.constraints) c.apply(this); + worldUnpack.setFrom(world); if (scaleWorld != 1.0) { worldUnpack._00 *= scaleWorld; @@ -172,9 +180,6 @@ class Transform { worldUnpack._23 *= scaleWorld; } - // Constraints - if (object.constraints != null) for (c in object.constraints) c.apply(this); - computeDim(); // Update children @@ -265,7 +270,7 @@ class Transform { } function computeRadius() { - radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z); + radius = 0.5 * Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z); } function computeDim() { diff --git a/leenkx/Sources/iron/object/Uniforms.hx b/leenkx/Sources/iron/object/Uniforms.hx index d12f7a67..b404bb1b 100644 --- a/leenkx/Sources/iron/object/Uniforms.hx +++ b/leenkx/Sources/iron/object/Uniforms.hx @@ -428,12 +428,10 @@ class Uniforms { var v: Vec4 = null; helpVec.set(0, 0, 0, 0); switch (c.link) { - #if lnx_debug case "_input": { helpVec.set(Input.getMouse().x / iron.App.w(), Input.getMouse().y / iron.App.h(), Input.getMouse().down() ? 1.0 : 0.0, 0.0); v = helpVec; } - #end default: return false; } @@ -690,7 +688,7 @@ class Uniforms { } case "_hosekSunDirection": { var w = Scene.active.world; - if (w != null) { + if (w != null && w.raw.sun_direction != null) { // Clamp Z for night cycle helpVec.set(w.raw.sun_direction[0], w.raw.sun_direction[1], @@ -956,6 +954,22 @@ class Uniforms { } static function setObjectConstant(g: Graphics, object: Object, location: ConstantLocation, c: TShaderConstant) { + #if lnx_spot + if (c.name == "LWVPSpot") { + var light = getSpot(0); + if (light != null) { + if (object == null) helpMat.setIdentity(); + else helpMat.setFrom(object.transform.worldUnpack); + + helpMat.multmat(light.VP); + helpMat.multmat(biasMat); + + g.setMatrix(location, helpMat.self); + return; + } + } + #end + if (c.link == null) return; var camera = Scene.active.camera; @@ -1263,17 +1277,17 @@ class Uniforms { var vy: Null = null; switch (c.link) { case "_tilesheetOffset": { - var ts = cast(object, MeshObject).tilesheet; + var ts = cast(object, MeshObject).activeTilesheet; vx = ts.tileX; vy = ts.tileY; } case "_tilesheetFlip": { - var ts = cast(object, MeshObject).tilesheet; + var ts = cast(object, MeshObject).activeTilesheet; vx = ts.flipX ? 1.0 : 0.0; vy = ts.flipY ? 1.0 : 0.0; } case "_tilesheetTiles": { - var ts = cast(object, MeshObject).tilesheet; + var ts = cast(object, MeshObject).activeTilesheet; vx = ts.getTilesX(); vy = ts.getTilesY(); } diff --git a/leenkx/Sources/leenkx/logicnode/ActiveSceneObjectNode.hx b/leenkx/Sources/leenkx/logicnode/ActiveSceneObjectNode.hx new file mode 100644 index 00000000..18fee977 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ActiveSceneObjectNode.hx @@ -0,0 +1,12 @@ +package leenkx.logicnode; + +class ActiveSceneObjectNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + return iron.Scene.active.root.getChild(iron.Scene.active.raw.name); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx b/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx index 456a27aa..76e31496 100644 --- a/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx @@ -3,6 +3,8 @@ package leenkx.logicnode; import iron.data.SceneFormat.TSceneFormat; import iron.data.Data; import iron.object.Object; +import iron.object.MeshObject; +import iron.Scene; class AddParticleToObjectNode extends LogicNode { @@ -22,22 +24,22 @@ class AddParticleToObjectNode extends LogicNode { if (objFrom == null || objTo == null) return; - var mobjFrom = cast(objFrom, iron.object.MeshObject); + var mobjFrom = cast(objFrom, MeshObject); var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] : mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null; if (psys == null) return; - var mobjTo = cast(objTo, iron.object.MeshObject); - - mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name}); + var mobjTo = cast(objTo, MeshObject); + + mobjTo.setupParticleSystem(Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name}); mobjTo.render_emitter = inputs[4].get(); - iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) { + Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) { if (o != null) { - var c: iron.object.MeshObject = cast o; + var c: MeshObject = cast o; if (mobjTo.particleChildren == null) mobjTo.particleChildren = []; mobjTo.particleChildren.push(c); c.particleOwner = mobjTo; @@ -55,7 +57,7 @@ class AddParticleToObjectNode extends LogicNode { var slot: Int = inputs[3].get(); var mobjTo: Object = inputs[4].get(); - var mobjTo = cast(mobjTo, iron.object.MeshObject); + var mobjTo = cast(mobjTo, MeshObject); #if lnx_json sceneName += ".json"; @@ -65,28 +67,34 @@ class AddParticleToObjectNode extends LogicNode { Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> { - for (obj in rawScene.objects) { - if (obj.name == objectName) { - mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]); - mobjTo.render_emitter = inputs[5].get(); + for (obj in rawScene.objects) { + if (obj.name == objectName) { + mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]); + mobjTo.render_emitter = inputs[5].get(); - iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) { - if (o != null) { - var c: iron.object.MeshObject = cast o; - if (mobjTo.particleChildren == null) mobjTo.particleChildren = []; - mobjTo.particleChildren.push(c); - c.particleOwner = mobjTo; - c.particleIndex = mobjTo.particleChildren.length - 1; - } - }, true, rawScene); + for (i => ps in rawScene.particle_datas) + if (obj.particle_refs[slot].particle == ps.name){ + slot = i; + break; + } - var oslot: Int = mobjTo.particleSystems.length-1; - var opsys = mobjTo.particleSystems[oslot]; - @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]); + Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) { + if (o != null) { + var c: MeshObject = cast o; + if (mobjTo.particleChildren == null) mobjTo.particleChildren = []; + mobjTo.particleChildren.push(c); + c.particleOwner = mobjTo; + c.particleIndex = mobjTo.particleChildren.length - 1; + } + }, true, rawScene); - break; + var oslot: Int = mobjTo.particleSystems.length-1; + var opsys = mobjTo.particleSystems[oslot]; + @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]); + + break; + } } - } }); diff --git a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx index f1628147..74dd79bf 100644 --- a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx @@ -111,7 +111,9 @@ class AddPhysicsConstraintNode extends LogicNode { } } } - pivotObject.addTrait(con); + con.name = property0; + + pivotObject.addTrait(con); } #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/AddPhysicsHookNode.hx b/leenkx/Sources/leenkx/logicnode/AddPhysicsHookNode.hx new file mode 100644 index 00000000..0712ab8b --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AddPhysicsHookNode.hx @@ -0,0 +1,46 @@ +package leenkx.logicnode; + +import iron.object.Object; +#if lnx_bullet +import leenkx.trait.physics.bullet.PhysicsHook; +#end + +class AddPhysicsHookNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + var target: Object = inputs[2].get(); + var inputVerts: Dynamic = inputs[3].get(); + + var flattenedVerts: Array = []; + + if (Std.isOfType(inputVerts, Array)) { + var vArray: Array = cast inputVerts; + for (v in vArray) { + if (v.x != null) { + flattenedVerts.push(v.x); + flattenedVerts.push(v.y); + flattenedVerts.push(v.z); + } + else if (v[0] != null) { + flattenedVerts.push(v[0]); + flattenedVerts.push(v[1]); + flattenedVerts.push(v[2]); + } + } + } + +#if lnx_bullet + var hook = obj.getTrait(PhysicsHook); + if (hook == null) { + hook = new PhysicsHook(target.name, flattenedVerts); + obj.addTrait(hook); + } +#end + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx index 3df20cf8..b121bd25 100644 --- a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx @@ -20,7 +20,6 @@ class AddRigidBodyNode extends LogicNode { override function run(from: Int) { object = inputs[1].get(); - if (object == null) return; #if lnx_physics @@ -70,6 +69,7 @@ class AddRigidBodyNode extends LogicNode { case "Cylinder": shape = Cylinder; case "Convex Hull": shape = ConvexHull; case "Mesh": shape = Mesh; + case "Compound Parent": shape = Compound; } rb = new RigidBody(shape, mass, friction, bounciness, group, mask); @@ -77,6 +77,48 @@ class AddRigidBodyNode extends LogicNode { rb.staticObj = !active; rb.isTriggerObject(trigger); + if (property0 == "Compound Parent") { + var compoundChildren = []; + for (child in object.children) { + var childRb: RigidBody = child.getTrait(RigidBody); + if (childRb != null) { + var childShape = 0; + switch (@:privateAccess childRb.shape) { + case Box: childShape = 0; + case Sphere: childShape = 1; + case ConvexHull: childShape = 2; + case Mesh: childShape = 3; + case Cone: childShape = 4; + case Cylinder: childShape = 5; + case Capsule: childShape = 6; + default: childShape = 0; + } + childRb.remove(); + var m = object.transform.world.clone(); + m.getInverse(object.transform.world); + m.multmat(child.transform.world); + var loc = new iron.math.Vec4(); + var rot = new iron.math.Quat(); + var scl = new iron.math.Vec4(); + m.decompose(loc, rot, scl); + compoundChildren.push({ + shape: childShape, + posX: loc.x, + posY: loc.y, + posZ: loc.z, + rotX: rot.x, + rotY: rot.y, + rotZ: rot.z, + rotW: rot.w, + dimX: child.transform.dim.x, + dimY: child.transform.dim.y, + dimZ: child.transform.dim.z + }); + } + } + @:privateAccess rb.compoundChildren = compoundChildren; + } + if (property1) { rb.linearDamping = linDamp; rb.angularDamping = angDamp; diff --git a/leenkx/Sources/leenkx/logicnode/AddSoftBodyNode.hx b/leenkx/Sources/leenkx/logicnode/AddSoftBodyNode.hx new file mode 100644 index 00000000..ac4c12ee --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AddSoftBodyNode.hx @@ -0,0 +1,38 @@ +package leenkx.logicnode; + +import iron.object.Object; +#if lnx_physics_soft +import leenkx.trait.physics.bullet.SoftBody; +#end + +class AddSoftBodyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + if (obj == null) return; + + var shape: Int = inputs[2].get(); // 0: Cloth, 1: Volume + var bend: Float = inputs[3].get(); + var mass: Float = inputs[4].get(); + var margin: Float = inputs[5].get(); + var friction: Float = inputs[6].get(); + var damping: Float = inputs[7].get(); + var pressure: Float = inputs[8].get(); + var lStiff: Float = inputs[9].get(); + var aStiff: Float = inputs[10].get(); + + #if lnx_physics_soft + var sb: SoftBody = obj.getTrait(SoftBody); + if (sb == null) { + sb = new SoftBody(shape, bend, mass, margin, friction, damping, lStiff, aStiff, pressure); + obj.addTrait(sb); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AddTraitNode.hx b/leenkx/Sources/leenkx/logicnode/AddTraitNode.hx index 21ffa47f..8384a376 100644 --- a/leenkx/Sources/leenkx/logicnode/AddTraitNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddTraitNode.hx @@ -4,25 +4,38 @@ import iron.object.Object; class AddTraitNode extends LogicNode { + public var property0: String; + var trait: Dynamic; + public function new(tree: LogicTree) { super(tree); } override function run(from: Int) { var object: Object = inputs[1].get(); - var traitName: String = inputs[2].get(); - assert(Error, object != null, "Object should not be null"); - assert(Error, traitName != null, "Trait name should not be null"); - var cname = Type.resolveClass(Main.projectPackage + "." + traitName); - if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName); - assert(Error, cname != null, 'No trait with the name "$traitName" found, make sure that the trait is exported!'); - assert(Warning, object.getTrait(cname) == null, 'Object already has the trait "$traitName" applied'); + if (property0 == 'TraitName'){ + var traitName: String = inputs[2].get(); + + assert(Error, traitName != null, "Trait name should not be null"); + + var cname = Type.resolveClass(Main.projectPackage + "." + traitName); + if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName); + assert(Error, cname != null, 'No trait with the name "$traitName" found, make sure that the trait is exported!'); + assert(Warning, object.getTrait(cname) == null, 'Object already has the trait "$traitName" applied'); + + trait = Type.createInstance(cname, []); + + } else + trait = inputs[2].get(); - var trait = Type.createInstance(cname, []); object.addTrait(trait); runOutput(0); } + + override function get(from: Int): Dynamic { + return trait; + } } diff --git a/leenkx/Sources/leenkx/logicnode/AlongCurveNode.hx b/leenkx/Sources/leenkx/logicnode/AlongCurveNode.hx new file mode 100644 index 00000000..476ba675 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AlongCurveNode.hx @@ -0,0 +1,23 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.CurveObject; + +class AlongCurveNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var curve: CurveObject = inputs[2].get(); + var splineIdx: Int = inputs[3].get(); + var forwardAxis: String = inputs[4].get(); + var position: Float = inputs[5].get(); + + curve.follow(object, position, splineIdx, forwardAxis); + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AnimationNode.hx b/leenkx/Sources/leenkx/logicnode/AnimationNode.hx new file mode 100644 index 00000000..64b390a1 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AnimationNode.hx @@ -0,0 +1,35 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.Animation; +import iron.object.ObjectAnimation; + +class AnimationNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + + if (object == null) + return from == 0 ? null : 0; + + var animation: Animation = object.animation; + if (animation == null) animation = object.getParentArmature(object.name); + + var actions: Array = []; + + if (animation.isSkinned) + for(a in animation.armature.actions) + actions.push(a.name); + else + for (a in cast(animation, ObjectAnimation).oactions) + if (a != null) + actions.push(a.objects[0].name); + + return from == 0 ? actions : actions.length; + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AroundNavigableLocationNode.hx b/leenkx/Sources/leenkx/logicnode/AroundNavigableLocationNode.hx new file mode 100644 index 00000000..1d9dafbc --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AroundNavigableLocationNode.hx @@ -0,0 +1,29 @@ +package leenkx.logicnode; + +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +#end + +import iron.object.Object; +import iron.math.Vec4; + +class AroundNavigableLocationNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + + } + + override function get(from: Int): Dynamic { + #if lnx_navigation + var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(inputs[0].get()); + var position: Vec4 = inputs[1].get(); + var radius: Float = inputs[2].get(); + + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + return activeNavMesh.getRandomPointAround(position, radius); + #end + return null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ArrayAddNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayAddNode.hx index d1e29fef..94c1bf36 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayAddNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayAddNode.hx @@ -12,7 +12,7 @@ class ArrayAddNode extends LogicNode { override function run(from: Int) { ar = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); // "Modify Original" == `false` -> Copy the input array if (!inputs[2].get()) { diff --git a/leenkx/Sources/leenkx/logicnode/ArrayGetNextNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayGetNextNode.hx index 23204ac1..a3fcc687 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayGetNextNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayGetNextNode.hx @@ -2,7 +2,7 @@ package leenkx.logicnode; class ArrayGetNextNode extends LogicNode { - var i = 0; + var i = -1; public function new(tree: LogicTree) { super(tree); @@ -13,12 +13,12 @@ class ArrayGetNextNode extends LogicNode { if (ar == null) return null; - var value = ar[i]; - if (i < ar.length - 1) i++; else i = 0; + + var value = ar[i]; return value; diff --git a/leenkx/Sources/leenkx/logicnode/ArrayGetPreviousNextNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayGetPreviousNextNode.hx index 822a00ba..f4925ae6 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayGetPreviousNextNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayGetPreviousNextNode.hx @@ -2,7 +2,7 @@ package leenkx.logicnode; class ArrayGetPreviousNextNode extends LogicNode { - var i = 0; + var i = -1; public function new(tree: LogicTree) { super(tree); diff --git a/leenkx/Sources/leenkx/logicnode/ArrayInsertNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayInsertNode.hx index 7cd8cef8..937ba472 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayInsertNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayInsertNode.hx @@ -13,7 +13,7 @@ class ArrayInsertNode extends LogicNode { var index: Int = inputs[2].get(); var value: Dynamic = inputs[3].get(); - if (ar == null || value == null) return; + assert(Error, ar != null && value != null, 'Array or Value should not be null'); ar.insert(index, value); diff --git a/leenkx/Sources/leenkx/logicnode/ArrayLoopNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayLoopNode.hx index a1ab6622..990231b7 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayLoopNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayLoopNode.hx @@ -11,7 +11,7 @@ class ArrayLoopNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); index = -1; for (val in ar) { diff --git a/leenkx/Sources/leenkx/logicnode/ArrayRemoveNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayRemoveNode.hx index a909369f..84006c63 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayRemoveNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayRemoveNode.hx @@ -10,7 +10,7 @@ class ArrayRemoveNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); var i: Int = inputs[2].get(); if (i < 0) i = ar.length + i; diff --git a/leenkx/Sources/leenkx/logicnode/ArrayRemoveValueNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayRemoveValueNode.hx index 5746cd41..5d0d634c 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayRemoveValueNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayRemoveValueNode.hx @@ -10,7 +10,7 @@ class ArrayRemoveValueNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); var val: Dynamic = inputs[2].get(); diff --git a/leenkx/Sources/leenkx/logicnode/ArrayResizeNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayResizeNode.hx index fc6b4848..dba9d8bd 100644 --- a/leenkx/Sources/leenkx/logicnode/ArrayResizeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArrayResizeNode.hx @@ -8,7 +8,7 @@ class ArrayResizeNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); var len = inputs[2].get(); diff --git a/leenkx/Sources/leenkx/logicnode/ArraySetNode.hx b/leenkx/Sources/leenkx/logicnode/ArraySetNode.hx index a92adaee..2564d512 100644 --- a/leenkx/Sources/leenkx/logicnode/ArraySetNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArraySetNode.hx @@ -8,7 +8,7 @@ class ArraySetNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); var i: Int = inputs[2].get(); var value: Dynamic = inputs[3].get(); diff --git a/leenkx/Sources/leenkx/logicnode/ArraySpliceNode.hx b/leenkx/Sources/leenkx/logicnode/ArraySpliceNode.hx index d4fd234b..69533069 100644 --- a/leenkx/Sources/leenkx/logicnode/ArraySpliceNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ArraySpliceNode.hx @@ -10,7 +10,7 @@ class ArraySpliceNode extends LogicNode { override function run(from: Int) { var ar: Array = inputs[1].get(); - if (ar == null) return; + assert(Error, ar != null, 'Array should not be null'); var i = inputs[2].get(); var len = inputs[3].get(); diff --git a/leenkx/Sources/leenkx/logicnode/AsyncArrayLoopNode.hx b/leenkx/Sources/leenkx/logicnode/AsyncArrayLoopNode.hx new file mode 100644 index 00000000..959dca84 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AsyncArrayLoopNode.hx @@ -0,0 +1,62 @@ +package leenkx.logicnode; + +class AsyncArrayLoopNode extends LogicNode { + + var array:Array; + var index:Int = 0; + var running:Bool = false; + var itemsPerFrame:Int = 1; + + public function new(tree:LogicTree) { + super(tree); + } + + override function run(from: Int) { + array = inputs[1].get(); + itemsPerFrame = inputs[2].get(); + index = 0; + if (array == null || array.length == 0) { + runOutput(3); + return; + } + running = true; + tree.notifyOnUpdate(update); + } + + function update() { + if (!running) return; + + var processed = 0; + while (processed < itemsPerFrame && index < array.length) { + index++; + processed++; + runOutput(0); + + if (tree.loopBreak) { + tree.loopBreak = false; + running = false; + tree.removeUpdate(update); + runOutput(2); + return; + } + + if (tree.loopContinue) { + tree.loopContinue = false; + continue; + } + } + + if (index >= array.length) { + running = false; + tree.removeUpdate(update); + runOutput(3); + } + } + + override function get(from: Int): Dynamic { + if (from == 1) + return array[index - 1]; + return index - 1; + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/AsyncLoopNode.hx b/leenkx/Sources/leenkx/logicnode/AsyncLoopNode.hx new file mode 100644 index 00000000..72cf3fbd --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AsyncLoopNode.hx @@ -0,0 +1,63 @@ +package leenkx.logicnode; + +class AsyncLoopNode extends LogicNode { + + var from:Int; + var to:Int; + var index:Int; + var running:Bool = false; + var itemsPerFrame:Int = 1; + + public function new(tree:LogicTree) { + super(tree); + } + + override function run(from: Int) { + this.from = inputs[1].get(); + this.to = inputs[2].get(); + this.itemsPerFrame = inputs[3].get(); + index = this.from; + + if (this.from >= this.to) { + runOutput(2); + return; + } + + running = true; + tree.notifyOnUpdate(update); + } + + function update() { + if (!running) return; + + var processed = 0; + while (processed < itemsPerFrame && index < to) { + runOutput(0); + index++; + processed++; + + if (tree.loopBreak) { + tree.loopBreak = false; + running = false; + tree.removeUpdate(update); + runOutput(2); + return; + } + + if (tree.loopContinue) { + tree.loopContinue = false; + continue; + } + } + + if (index >= to) { + running = false; + tree.removeUpdate(update); + runOutput(2); + } + } + + override function get(from: Int): Dynamic { + return index - 1; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/CallFunctionNode.hx b/leenkx/Sources/leenkx/logicnode/CallFunctionNode.hx index c80ffb97..7cbbe938 100644 --- a/leenkx/Sources/leenkx/logicnode/CallFunctionNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CallFunctionNode.hx @@ -1,7 +1,5 @@ package leenkx.logicnode; -import iron.object.Object; - class CallFunctionNode extends LogicNode { var result: Dynamic; @@ -11,8 +9,8 @@ class CallFunctionNode extends LogicNode { } override function run(from: Int) { - var object: Dynamic = inputs[1].get(); - if (object == null) return; + var trait: Dynamic = inputs[1].get(); + if (trait == null){ runOutput(0); return; } var funName: String = inputs[2].get(); var args: Array = []; @@ -21,9 +19,9 @@ class CallFunctionNode extends LogicNode { args.push(inputs[i].get()); } - var func = Reflect.field(object, funName); + var func = Reflect.field(trait, funName); if (func != null) { - result = Reflect.callMethod(object, func, args); + result = Reflect.callMethod(trait, func, args); } runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/CameraGetNode.hx b/leenkx/Sources/leenkx/logicnode/CameraGetNode.hx index 2251116f..ed2e6157 100644 --- a/leenkx/Sources/leenkx/logicnode/CameraGetNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CameraGetNode.hx @@ -14,14 +14,16 @@ class CameraGetNode extends LogicNode { case 3: leenkx.renderpath.Postprocess.camera_uniforms[3];//Camera: Exposure Compensation case 4: leenkx.renderpath.Postprocess.camera_uniforms[4];//Fisheye Distortion case 5: leenkx.renderpath.Postprocess.camera_uniforms[5];//DoF AutoFocus §§ If true, it ignores the DoF Distance setting - case 6: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance - case 7: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm - case 8: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop - case 9: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method - case 10: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort - case 11: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain - case 12: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen - case 13: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette + case 6: new iron.math.Vec4(leenkx.renderpath.Postprocess.auto_focus[0], leenkx.renderpath.Postprocess.auto_focus[1], 0, 1); //Auto Focus Value + case 7: leenkx.renderpath.Postprocess.auto_focus[2]; //max blur + case 8: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance + case 9: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm + case 10: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop + case 11: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method + case 12: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort + case 13: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain + case 14: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen + case 15: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette default: 0.0; } } diff --git a/leenkx/Sources/leenkx/logicnode/CameraSetNode.hx b/leenkx/Sources/leenkx/logicnode/CameraSetNode.hx index 21c4ade9..f93b8269 100644 --- a/leenkx/Sources/leenkx/logicnode/CameraSetNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CameraSetNode.hx @@ -23,6 +23,11 @@ class CameraSetNode extends LogicNode { leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[1].get();//Fisheye Distortion case 'Auto Focus': leenkx.renderpath.Postprocess.camera_uniforms[5] = inputs[1].get();//DoF AutoFocus §§ If true, it ignores the DoF Distance setting + case 'Auto Focus Value': + leenkx.renderpath.Postprocess.auto_focus[0] = inputs[1].get().x; + leenkx.renderpath.Postprocess.auto_focus[1] = inputs[1].get().y; + case 'DoF Max Blur': + leenkx.renderpath.Postprocess.auto_focus[2] = inputs[1].get(); // DoF Max Blur case 'DoF Distance': leenkx.renderpath.Postprocess.camera_uniforms[6] = inputs[1].get();//DoF Distance case 'DoF Length': diff --git a/leenkx/Sources/leenkx/logicnode/CanvasSetSliderNode.hx b/leenkx/Sources/leenkx/logicnode/CanvasSetSliderNode.hx index 4b9b93f8..1e5a9ae2 100644 --- a/leenkx/Sources/leenkx/logicnode/CanvasSetSliderNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CanvasSetSliderNode.hx @@ -14,6 +14,7 @@ class CanvasSetSliderNode extends LogicNode { } #if lnx_ui + /* function update() { if (!canvas.ready) return; @@ -26,7 +27,7 @@ class CanvasSetSliderNode extends LogicNode { catch (e: Dynamic) {} runOutput(0); - } + }*/ override function run(from: Int) { element = inputs[1].get(); @@ -34,9 +35,15 @@ class CanvasSetSliderNode extends LogicNode { canvas = CanvasScript.getActiveCanvas(); + canvas.notifyOnReady(() -> { + canvas.getHandle(element).value = value; + runOutput(0); + + }); + // Ensure canvas is ready - tree.notifyOnUpdate(update); - update(); + //tree.notifyOnUpdate(update); + //update(); } #end } diff --git a/leenkx/Sources/leenkx/logicnode/ColorMixNode.hx b/leenkx/Sources/leenkx/logicnode/ColorMixNode.hx index 7b910fa0..e7e3e66d 100644 --- a/leenkx/Sources/leenkx/logicnode/ColorMixNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ColorMixNode.hx @@ -4,148 +4,15 @@ import iron.math.Vec4; class ColorMixNode extends LogicNode { - var SIZE:Int = 38; - var GAMMA:Float = 2.4; - var EPSILON:Float = 0.00000001; - - var SPD_C:Array = [0.96853629, 0.96855103, 0.96859338, 0.96877345, 0.96942204, 0.97143709, 0.97541862, 0.98074186, 0.98580992, 0.98971194, 0.99238027, 0.99409844, 0.995172, 0.99576545, 0.99593552, 0.99564041, 0.99464769, 0.99229579, 0.98638762, 0.96829712, 0.89228016, 0.53740239, 0.15360445, 0.05705719, 0.03126539, 0.02205445, 0.01802271, 0.0161346, 0.01520947, 0.01475977, 0.01454263, 0.01444459, 0.01439897, 0.0143762, 0.01436343, 0.01435687, 0.0143537, 0.01435408]; - var SPD_M:Array = [0.51567122, 0.5401552, 0.62645502, 0.75595012, 0.92826996, 0.97223624, 0.98616174, 0.98955255, 0.98676237, 0.97312575, 0.91944277, 0.32564851, 0.13820628, 0.05015143, 0.02912336, 0.02421691, 0.02660696, 0.03407586, 0.04835936, 0.0001172, 0.00008554, 0.85267882, 0.93188793, 0.94810268, 0.94200977, 0.91478045, 0.87065445, 0.78827548, 0.65738359, 0.59909403, 0.56817268, 0.54031997, 0.52110241, 0.51041094, 0.50526577, 0.5025508, 0.50126452, 0.50083021]; - var SPD_Y:Array = [0.02055257, 0.02059936, 0.02062723, 0.02073387, 0.02114202, 0.02233154, 0.02556857, 0.03330189, 0.05185294, 0.10087639, 0.24000413, 0.53589066, 0.79874659, 0.91186529, 0.95399623, 0.97137099, 0.97939505, 0.98345207, 0.98553736, 0.98648905, 0.98674535, 0.98657555, 0.98611877, 0.98559942, 0.98507063, 0.98460039, 0.98425301, 0.98403909, 0.98388535, 0.98376116, 0.98368246, 0.98365023, 0.98361309, 0.98357259, 0.98353856, 0.98351247, 0.98350101, 0.98350852]; - var SPD_R:Array = [0.03147571, 0.03146636, 0.03140624, 0.03119611, 0.03053888, 0.02856855, 0.02459485, 0.0192952, 0.01423112, 0.01033111, 0.00765876, 0.00593693, 0.00485616, 0.00426186, 0.00409039, 0.00438375, 0.00537525, 0.00772962, 0.0136612, 0.03181352, 0.10791525, 0.46249516, 0.84604333, 0.94275572, 0.96860996, 0.97783966, 0.98187757, 0.98377315, 0.98470202, 0.98515481, 0.98537114, 0.98546685, 0.98550011, 0.98551031, 0.98550741, 0.98551323, 0.98551563, 0.98551547]; - var SPD_G:Array = [0.49108579, 0.46944057, 0.4016578, 0.2449042, 0.0682688, 0.02732883, 0.013606, 0.01000187, 0.01284127, 0.02636635, 0.07058713, 0.70421692, 0.85473994, 0.95081565, 0.9717037, 0.97651888, 0.97429245, 0.97012917, 0.9425863, 0.99989207, 0.99989891, 0.13823139, 0.06968113, 0.05628787, 0.06111561, 0.08987709, 0.13656016, 0.22169624, 0.32176956, 0.36157329, 0.4836192, 0.46488579, 0.47440306, 0.4857699, 0.49267971, 0.49625685, 0.49807754, 0.49889859]; - var SPD_B:Array = [0.97901834, 0.97901649, 0.97901118, 0.97892146, 0.97858555, 0.97743705, 0.97428075, 0.96663223, 0.94822893, 0.89937713, 0.76070164, 0.4642044, 0.20123039, 0.08808402, 0.04592894, 0.02860373, 0.02060067, 0.01656701, 0.01451549, 0.01357964, 0.01331243, 0.01347661, 0.01387181, 0.01435472, 0.01479836, 0.0151525, 0.01540513, 0.01557233, 0.0156571, 0.01571025, 0.01571916, 0.01572133, 0.01572502, 0.01571717, 0.01571905, 0.01571059, 0.01569728, 0.0157002]; - var CIE_CMF_X:Array = [0.00006469, 0.00021941, 0.00112057, 0.00376661, 0.01188055, 0.02328644, 0.03455942, 0.03722379, 0.03241838, 0.02123321, 0.01049099, 0.00329584, 0.00050704, 0.00094867, 0.00627372, 0.01686462, 0.02868965, 0.04267481, 0.05625475, 0.0694704, 0.08305315, 0.0861261, 0.09046614, 0.08500387, 0.07090667, 0.05062889, 0.03547396, 0.02146821, 0.01251646, 0.00680458, 0.00346457, 0.00149761, 0.0007697, 0.00040737, 0.00016901, 0.00009522, 0.00004903, 0.00002]; - var CIE_CMF_Y:Array = [0.00000184, 0.00000621, 0.00003101, 0.00010475, 0.00035364, 0.00095147, 0.00228226, 0.00420733, 0.0066888, 0.0098884, 0.01524945, 0.02141831, 0.03342293, 0.05131001, 0.07040208, 0.08783871, 0.09424905, 0.09795667, 0.09415219, 0.08678102, 0.07885653, 0.0635267, 0.05374142, 0.04264606, 0.03161735, 0.02088521, 0.01386011, 0.00810264, 0.0046301, 0.00249138, 0.0012593, 0.00054165, 0.00027795, 0.00014711, 0.00006103, 0.00003439, 0.00001771, 0.00000722]; - var CIE_CMF_Z:Array = [0.00030502, 0.00103681, 0.00531314, 0.01795439, 0.05707758, 0.11365162, 0.17335873, 0.19620658, 0.18608237, 0.13995048, 0.08917453, 0.04789621, 0.02814563, 0.01613766, 0.0077591, 0.00429615, 0.00200551, 0.00086147, 0.00036904, 0.00019143, 0.00014956, 0.00009231, 0.00006813, 0.00002883, 0.00001577, 0.00000394, 0.00000158, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; - var XYZ_RGB:Array> = [[3.24306333, -1.53837619, -0.49893282], [-0.96896309, 1.87542451, 0.04154303], [0.05568392, -0.20417438, 1.05799454]]; - - public function linearToConcentration(l1:Float, l2:Float, t:Float):Float { - var t1 = l1 * Math.pow(1 - t, 2); - var t2 = l2 * Math.pow(t, 2); - return t2 / (t1 + t2); - } - - public function spectralMix(color1:Array, color2:Array, t:Float):Array { - var lrgb1 = srgbToLinear(color1); - var lrgb2 = srgbToLinear(color2); - - var R1 = linearToReflectance(lrgb1); - var R2 = linearToReflectance(lrgb2); - - var l1 = dotProduct(R1, CIE_CMF_Y); - var l2 = dotProduct(R2, CIE_CMF_Y); - - t = linearToConcentration(l1, l2, t); - - var R:Array = new Array(); - for (i in 0...SIZE) { - var KS = (1 - t) * Math.pow((1 - R1[i]), 2) / (2 * R1[i]) + t * Math.pow((1 - R2[i]), 2) / (2 * R2[i]); - var KM = 1 + KS - Math.sqrt(KS * KS + 2 * KS); - R.push(KM); - } - - var xyz = reflectanceToXYZ(R); - return xyzToSrgb(xyz); - } - - public function uncompand(x:Float):Float { - return x < 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, GAMMA); - } - - public function compand(x:Float):Float { - return x < 0.0031308 ? x * 12.92 : 1.055 * Math.pow(x, 1.0 / GAMMA) - 0.055; - } - - public function srgbToLinear(srgb:Array):Array { - var r = uncompand(srgb[0] / 255); - var g = uncompand(srgb[1] / 255); - var b = uncompand(srgb[2] / 255); - return [r, g, b]; - } - - public function linearToSrgb(lrgb:Array):Array { - var r = compand(lrgb[0]); - var g = compand(lrgb[1]); - var b = compand(lrgb[2]); - return [Math.round(clamp(r, 0, 1) * 255), Math.round(clamp(g, 0, 1) * 255), Math.round(clamp(b, 0, 1) * 255)]; - } - - public function reflectanceToXYZ(R:Array):Array { - var x = dotProduct(R, CIE_CMF_X); - var y = dotProduct(R, CIE_CMF_Y); - var z = dotProduct(R, CIE_CMF_Z); - return [x, y, z]; - } - - public function xyzToSrgb(xyz:Array):Array { - var r = dotProduct(XYZ_RGB[0], xyz); - var g = dotProduct(XYZ_RGB[1], xyz); - var b = dotProduct(XYZ_RGB[2], xyz); - return linearToSrgb([r, g, b]); - } - - public function spectralUpsampling(lrgb:Array):Array { - var w = Math.min(Math.min(lrgb[0], lrgb[1]), lrgb[2]); - - var lrgbNew = [lrgb[0] - w, lrgb[1] - w, lrgb[2] - w]; - - var c = Math.min(lrgbNew[1], lrgbNew[2]); - var m = Math.min(lrgbNew[0], lrgbNew[2]); - var y = Math.min(lrgbNew[0], lrgbNew[1]); - var r = Math.max(0, Math.min(lrgbNew[0] - lrgbNew[2], lrgbNew[0] - lrgbNew[1])); - var g = Math.max(0, Math.min(lrgbNew[1] - lrgbNew[2], lrgbNew[1] - lrgbNew[0])); - var b = Math.max(0, Math.min(lrgbNew[2] - lrgbNew[1], lrgbNew[2] - lrgbNew[0])); - - return [w, c, m, y, r, g, b]; - } - - public function linearToReflectance(lrgb:Array):Array { - var weights = spectralUpsampling(lrgb); - - var R:Array = new Array(); - for (i in 0...SIZE) { - R[i] = Math.max(EPSILON, - weights[0] - + weights[1] * SPD_C[i] - + weights[2] * SPD_M[i] - + weights[3] * SPD_Y[i] - + weights[4] * SPD_R[i] - + weights[5] * SPD_G[i] - + weights[6] * SPD_B[i] - ); - } - - return R; - } - - public function dotProduct(a:Array, b:Array):Float { - var sum:Float = 0; - for (i in 0...a.length) { - sum += a[i] * b[i]; - } - return sum; - } - - public function clamp(value:Float, minValue:Float, maxValue:Float):Float { - return Math.min(Math.max(value, minValue), maxValue); - } - public function new(tree: LogicTree) { super(tree); } override function get(from: Int): Dynamic { - var Color1:Vec4 = inputs[0].get(); - var Color2:Vec4 = inputs[1].get(); - var mix:Float = inputs[2].get(); - - mix = Math.min(Math.max(mix, 0), 1); - - var col1 = [Std.int(Color1.x*255), Std.int(Color1.y*255), Std.int(Color1.z*255)]; - var col2 = [Std.int(Color2.x*255), Std.int(Color2.y*255), Std.int(Color2.z*255)]; - - var ColorMix = spectralMix(col1, col2, mix); - - return new Vec4(ColorMix[0]/255, ColorMix[1]/255, ColorMix[2]/255, (Color1.w+Color2.w)/2); + var c1: Vec4 = inputs[0].get(); + var c2: Vec4 = inputs[1].get(); + var factor: Float = inputs[2].get(); + + return leenkx.trait.internal.Spectral.mix([c1, c2], [1.0 - factor, factor]); } } diff --git a/leenkx/Sources/leenkx/logicnode/ColorsMixNode.hx b/leenkx/Sources/leenkx/logicnode/ColorsMixNode.hx new file mode 100644 index 00000000..6cd823a9 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ColorsMixNode.hx @@ -0,0 +1,26 @@ +package leenkx.logicnode; + +import iron.math.Vec4; +import leenkx.trait.internal.Spectral; + +class ColorsMixNode extends LogicNode { + + var result = new Vec4(); + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var colors: Array = inputs[0].get(); + var factors: Array = inputs[1].get(); + + if (colors == null || factors == null || colors.length == 0 || factors.length == 0) { + return result; + } + + result.setFrom(Spectral.mix(colors, factors)); + + return result; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ConvexBreakNode.hx b/leenkx/Sources/leenkx/logicnode/ConvexBreakNode.hx new file mode 100644 index 00000000..f37fe21b --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ConvexBreakNode.hx @@ -0,0 +1,73 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.MeshObject; +import leenkx.object.BreakerExtension; +import iron.math.Vec4; + +class ConvexBreakNode extends LogicNode { + + public var property0: String; + + var objects: Array; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + objects = []; + var object: Object = inputs[1].get(); + + var breaker: ConvexBreaker = new ConvexBreaker(0.1); + @:privateAccess breaker.scaleUV = inputs[3].get(); + @:privateAccess breaker.flatShading = inputs[4].get(); + + breaker.initBreakableObject(cast object, 0, 0, new Vec4(), new Vec4(), true); + + var debris: Array = []; + + if (property0 == 'Plane') + debris = breaker.subdivideByPlane(cast object, inputs[3].get(), inputs[2].get()); + else + debris = breaker.subdivideByImpact(cast object, inputs[2].get(), inputs[3].get(), 1, 1); + + for (o in debris) { + var obj: Object = cast o; + obj.name = o.data.raw.name; + + var dims = new kha.arrays.Float32Array(3); + dims[0] = o.data.geom.aabb.x; + dims[1] = o.data.geom.aabb.y; + dims[2] = o.data.geom.aabb.z; + + obj.raw = cast { + type: "mesh_object", + name: obj.name, + data_ref: obj.name, + dimensions: dims + }; + + obj.addTrait(new leenkx.trait.internal.UniformsManager()); + + var ud = breaker.userDataMap.get(cast o); + if (ud == null) continue; + objects.push(obj); + } + + if (objects.length > 1){ + for (obj in objects) + obj.setParent(iron.Scene.active.root); + object.remove(); + runOutput(1); + } + else + runOutput(2); + + runOutput(0); + } + + override function get(from: Int): Dynamic { + return objects; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/CreateMapNode.hx b/leenkx/Sources/leenkx/logicnode/CreateMapNode.hx index 4c8f1899..eda99302 100644 --- a/leenkx/Sources/leenkx/logicnode/CreateMapNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CreateMapNode.hx @@ -1,7 +1,6 @@ package leenkx.logicnode; import iron.object.Object; import iron.math.Vec4; -import leenkx.system.Event; class CreateMapNode extends LogicNode { diff --git a/leenkx/Sources/leenkx/logicnode/CrowdGoToLocationNode.hx b/leenkx/Sources/leenkx/logicnode/CrowdGoToLocationNode.hx index dec888c4..0a731acb 100644 --- a/leenkx/Sources/leenkx/logicnode/CrowdGoToLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/CrowdGoToLocationNode.hx @@ -2,6 +2,8 @@ package leenkx.logicnode; #if lnx_navigation import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +import leenkx.trait.NavCrowd; #end import iron.object.Object; @@ -9,24 +11,30 @@ import iron.math.Vec4; class CrowdGoToLocationNode extends LogicNode { - var object: Object; - var location: Vec4; - public function new(tree: LogicTree) { super(tree); } override function run(from: Int) { - object = inputs[1].get(); - location = inputs[2].get(); - + var navMeshId: String = inputs[1].get(); + var object: Object = inputs[2].get(); + var location: Vec4 = inputs[3].get(); + var maxSpeed: Float = inputs[4].get(); + var maxAcceleration: Float = inputs[5].get(); + var turnSpeed: Float = inputs[6].get(); + assert(Error, object != null, "The object input not be null"); assert(Error, location != null, "The location to navigate to must not be null"); #if lnx_navigation - assert(Error, Navigation.active.navMeshes.length > 0, "No Navigation Mesh Present"); - var crowdAgent: leenkx.trait.NavCrowd = object.getTrait(leenkx.trait.NavCrowd); + var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(navMeshId); + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + + var crowdAgent: NavCrowd = object.getTrait(NavCrowd); assert(Error, crowdAgent != null, "Object does not have a NavCrowd trait"); + crowdAgent.crowdAgentSetMaxSpeed(maxSpeed); + crowdAgent.crowdAgentSetMaxAcceleration(maxAcceleration); + crowdAgent.turnSpeed = turnSpeed; crowdAgent.crowdAgentGoto(location); #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/CrowdSetLocationNode.hx b/leenkx/Sources/leenkx/logicnode/CrowdSetLocationNode.hx new file mode 100644 index 00000000..113f4c72 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/CrowdSetLocationNode.hx @@ -0,0 +1,33 @@ +package leenkx.logicnode; + +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +import leenkx.trait.NavCrowd; +#end + +import iron.object.Object; +import iron.math.Vec4; + +class CrowdSetLocationNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var navMeshId: String = inputs[1].get(); + var object: Object = inputs[2].get(); + var location: Vec4 = inputs[3].get(); + + #if lnx_navigation + var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(navMeshId); + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + + var crowdAgent: NavCrowd = object.getTrait(NavCrowd); + assert(Error, crowdAgent != null, "Object does not have a NavCrowd trait"); + crowdAgent.crowdAgentTeleport(location); + #end + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/CurveGuideNode.hx b/leenkx/Sources/leenkx/logicnode/CurveGuideNode.hx new file mode 100644 index 00000000..4a6b7192 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/CurveGuideNode.hx @@ -0,0 +1,33 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.MeshObject; + +class CurveGuideNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + #if lnx_cpu_particles + var object: Object = inputs[1].get(); + var slot: Int = inputs[2].get(); + + if (object == null){ runOutput(0); return; } + + var mo: MeshObject = cast object; + + var psys = mo.particleSystems != null ? mo.particleSystems[slot] : null; + + if (psys == null){ runOutput(0); return; } + + psys.curveGuides = inputs[3].get(); + psys.curveGuideStrength = inputs[4].get(); + psys.curveGuideSpeed = inputs[5].get(); + + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/DeformCurveNode.hx b/leenkx/Sources/leenkx/logicnode/DeformCurveNode.hx new file mode 100644 index 00000000..7b3941bc --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/DeformCurveNode.hx @@ -0,0 +1,27 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; +import iron.object.MeshObject; +import iron.data.MeshData; + +class DeformCurveNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var mo: MeshObject = inputs[1].get(); + var curve: CurveObject = inputs[2].get(); + + var mData: MeshData = curve.generateDeformedMesh(mo.data, inputs[3].get(), inputs[4].get(), 1, inputs[5].get(), inputs[6].get()); + + if (mData != null){ + mo.setData(mData); + mo.transform.scale.set(1, 1, 1); + mo.transform.buildMatrix(); + } + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/DrawCameraNode.hx b/leenkx/Sources/leenkx/logicnode/DrawCameraNode.hx index ce611ace..ad1d077c 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawCameraNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawCameraNode.hx @@ -8,11 +8,9 @@ import iron.object.CameraObject; import leenkx.renderpath.RenderPathCreator; class DrawCameraNode extends LogicNode { - static inline var numStaticInputs = 2; - - var cameras: Array; - var renderTargets: Array; - var positions: Array; + var camera: CameraObject; + var renderTarget: kha.Image; + var position: Vec2 = new Vec2(); public function new(tree: LogicTree) { super(tree); @@ -21,39 +19,18 @@ class DrawCameraNode extends LogicNode { override function run(from: Int) { switch (from) { case 0: // Start - if (cameras == null) { - final numDynamicInputs = inputs.length - numStaticInputs; - final numCams = Std.int(numDynamicInputs / 5); + camera = inputs[2].get(); + position.set( + inputs[3].get(), + inputs[4].get() + ); - // Preallocate - cameras = []; - cameras.resize(numCams); - - positions = []; - positions.resize(numCams); - for (i in 0...positions.length) { - positions[i] = new Vec2(); - } - - renderTargets = []; - renderTargets.resize(numCams); - } - - for (i in 0...cameras.length) { - cameras[i] = inputs[numStaticInputs + i * 5].get(); - positions[i].set( - inputs[numStaticInputs + i * 5 + 1].get(), - inputs[numStaticInputs + i * 5 + 2].get() - ); - - // TODO: implement proper rendertarget cache/pool - renderTargets[i] = kha.Image.createRenderTarget( - inputs[numStaticInputs + i * 5 + 3].get(), // w - inputs[numStaticInputs + i * 5 + 4].get(), // h - kha.graphics4.TextureFormat.RGBA32, - kha.graphics4.DepthStencilFormat.NoDepthAndStencil - ); - } + renderTarget = kha.Image.createRenderTarget( + inputs[5].get(), // w + inputs[6].get(), // h + kha.graphics4.TextureFormat.RGBA32, + kha.graphics4.DepthStencilFormat.NoDepthAndStencil + ); tree.notifyOnRender(render); tree.notifyOnRender2D(render2D); @@ -67,47 +44,45 @@ class DrawCameraNode extends LogicNode { } function render(g:kha.graphics4.Graphics) { + if (inputs[7].get()) return; final rpPaused = RenderPath.active.paused; RenderPath.active.paused = false; final sceneCam = iron.Scene.active.camera; - for (i in 0...cameras.length) { - final cam = cameras[i]; + final cam = camera; - final oldRT = cam.renderTarget; - cam.renderTarget = renderTargets[i]; + final oldRT = cam.renderTarget; + cam.renderTarget = renderTarget; - iron.Scene.active.camera = cam; - cam.renderFrame(g); + iron.Scene.active.camera = cam; + cam.renderFrame(g); - cam.renderTarget = oldRT; - } + cam.renderTarget = oldRT; iron.Scene.active.camera = sceneCam; RenderPath.active.paused = rpPaused; } function render2D(g: kha.graphics2.Graphics) { - for(i in 0...cameras.length) { - final rt = renderTargets[i]; + if (inputs[7].get()) return; + final rt = renderTarget; - positions[i].set( - inputs[numStaticInputs + i * 5 + 1].get(), - inputs[numStaticInputs + i * 5 + 2].get() - ); + position.set( + inputs[3].get(), + inputs[4].get() + ); - final posX = positions[i].x; - final posY = positions[i].y; + final posX = position.x; + final posY = position.y; - g.color = 0xff000000; - g.fillRect(posX, posY, rt.width, rt.height); - g.color = 0xffffffff; - - if (kha.Image.renderTargetsInvertedY()) - g.drawScaledImage(rt, posX, posY+rt.height, rt.width, -rt.height); - else - g.drawScaledImage(rt, posX, posY, rt.width, rt.height); - } + g.color = 0xff000000; + g.fillRect(posX, posY, rt.width, rt.height); + g.color = 0xffffffff; + + if (kha.Image.renderTargetsInvertedY()) + g.drawScaledImage(rt, posX, posY+rt.height, rt.width, -rt.height); + else + g.drawScaledImage(rt, posX, posY, rt.width, rt.height); } } diff --git a/leenkx/Sources/leenkx/logicnode/DrawGifNode.hx b/leenkx/Sources/leenkx/logicnode/DrawGifNode.hx new file mode 100644 index 00000000..c1264f44 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/DrawGifNode.hx @@ -0,0 +1,129 @@ +package leenkx.logicnode; + +import iron.math.Vec4; +import kha.Image; +import kha.Color; +import leenkx.renderpath.RenderToTexture; + +import iron.format.gif.Reader; +import iron.format.gif.Data; +import iron.format.gif.Tools; + +import haxe.io.Bytes; +import haxe.io.BytesInput; + +import leenkx.system.Event; + +class DrawGifNode extends LogicNode { + var data:Data = null; + var frames:Int = 0; + var img: Array = []; + var lastImgName = ""; + var duration = 0.0; + var index = 0; + var i = 0; + + public function new(tree: LogicTree) { + super(tree); + + Event.add('load', function() { + var extractedBytes:Bytes = Tools.extractFullRGBA(data, i); + img.push(kha.Image.fromBytes(extractedBytes, data.logicalScreenDescriptor.width, data.logicalScreenDescriptor.height, kha.graphics4.TextureFormat.RGBA32)); + }); + + tree.notifyOnRemove(onRemove); + + } + + function onRemove() { + Event.remove('load'); + } + + override function run(from: Int) { + if (from == 0){ + + if (data == null){ + runOutput(0); + return; + } + + RenderToTexture.ensure2DContext("DrawGifNode"); + + final colorVec: Vec4 = inputs[3].get(); + final anchorH: Int = inputs[4].get(); + final anchorV: Int = inputs[5].get(); + final x: Float = inputs[6].get(); + final y: Float = inputs[7].get(); + final width: Float = inputs[8].get(); + final height: Float = inputs[9].get(); + final angle: Float = inputs[10].get(); + + var sindex: Int = inputs[11].get(); + var eindex: Int = inputs[12].get(); + final fdur: Float = inputs[13].get(); + final loop: Bool = inputs[14].get(); + + final drawx = x - 0.5 * width * anchorH; + final drawy = y - 0.5 * height * anchorV; + + if(eindex == -1 || eindex > frames) + eindex = frames; + + if(sindex < 0 || sindex > frames) + sindex = 0; + + if (index < sindex || index > eindex) + index = sindex; + + duration += iron.system.Time.delta; + if (duration >= fdur){ + if (index < eindex) + index += 1; + else + if (loop) index = sindex; + duration = 0; + if (i < eindex){ + ++i; + Event.send('load'); + } + } + + if (img.length > (index - sindex)){ + RenderToTexture.g.rotate(angle, x, y); + RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w); + RenderToTexture.g.drawScaledImage(img[index-sindex], drawx, drawy, width, height); + RenderToTexture.g.rotate(-angle, x, y); + } + + runOutput(0); + } + else{ + final imgName: String = inputs[2].get(); + if (imgName != lastImgName) { + lastImgName = imgName; + img = []; + i = inputs[11].get(); + index = i; + iron.data.Data.getBlob(imgName, (blob: kha.Blob) -> { + var bytes: Bytes = blob.toBytes(); + var input: BytesInput = new BytesInput(bytes); + data = new Reader(input).read(); + frames = Tools.framesCount(data); + if (i > frames) return; + var extractedBytes:Bytes = Tools.extractFullRGBA(data, i); + img.push(kha.Image.fromBytes(extractedBytes, data.logicalScreenDescriptor.width, data.logicalScreenDescriptor.height, kha.graphics4.TextureFormat.RGBA32)); + ++i; + Event.send('load'); + }); + } + + } + } + + override function get(from: Int): Dynamic { + if (from == 1) + return frames; + else + return index; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/DrawImageRenderNode.hx b/leenkx/Sources/leenkx/logicnode/DrawImageRenderNode.hx index 892b2728..040c7344 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawImageRenderNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawImageRenderNode.hx @@ -1,12 +1,15 @@ package leenkx.logicnode; import iron.math.Vec4; +import iron.RenderPath; import kha.Image; import kha.Color; import leenkx.renderpath.RenderToTexture; class DrawImageRenderNode extends LogicNode { var img: Image; + var img2D: Image; + var initialized: Bool = false; public function new(tree: LogicTree) { super(tree); @@ -14,8 +17,21 @@ class DrawImageRenderNode extends LogicNode { override function run(from: Int) { - if (from == 1) + if (from == 1){ + if (inputs[16].get()){ + if (initialized) return; + initialized = true; + } + + img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(), + kha.graphics4.TextureFormat.RGBA32, + kha.graphics4.DepthStencilFormat.NoDepthAndStencil); + if (inputs[15].get() || kha.Image.renderTargetsInvertedY()) + img2D = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(), + kha.graphics4.TextureFormat.RGBA32, + kha.graphics4.DepthStencilFormat.NoDepthAndStencil); tree.notifyOnRender(render); + } else { RenderToTexture.ensure2DContext("DrawImageRenderNode"); @@ -38,12 +54,14 @@ class DrawImageRenderNode extends LogicNode { RenderToTexture.g.rotate(angle, x, y); - if (img != null){ - RenderToTexture.g.color = 0xff000000; - RenderToTexture.g.fillRect(drawx, drawy, width, height); - RenderToTexture.g.color = RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w); + RenderToTexture.g.color = 0xff000000; + RenderToTexture.g.fillRect(drawx, drawy, width, height); + RenderToTexture.g.color = RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w); + + if ((inputs[15].get() || kha.Image.renderTargetsInvertedY()) && img2D != null) + RenderToTexture.g.drawScaledSubImage(img2D, sx, sy, swidth, sheight, drawx, drawy, width, height); + else if(img != null) RenderToTexture.g.drawScaledSubImage(img, sx, sy, swidth, sheight, drawx, drawy, width, height); - } RenderToTexture.g.rotate(-angle, x, y); @@ -54,13 +72,11 @@ class DrawImageRenderNode extends LogicNode { } function render(g: kha.graphics4.Graphics) { + final rpPaused = RenderPath.active.paused; + RenderPath.active.paused = false; var camera = inputs[2].get(); - img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(), - kha.graphics4.TextureFormat.RGBA32, - kha.graphics4.DepthStencilFormat.NoDepthAndStencil); - final sceneCam = iron.Scene.active.camera; final oldRT = camera.renderTarget; @@ -69,38 +85,30 @@ class DrawImageRenderNode extends LogicNode { camera.renderFrame(g); - img = camera.renderTarget; - if (inputs[15].get() || kha.Image.renderTargetsInvertedY()) { - img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(), - kha.graphics4.TextureFormat.RGBA32, - kha.graphics4.DepthStencilFormat.NoDepthAndStencil); + img2D.g2.begin(true, Color.Transparent); + img2D.g2.color = Color.White; - img.g2.begin(true, Color.Transparent); + if (kha.Image.renderTargetsInvertedY()) + img2D.g2.drawScaledImage(camera.renderTarget, 0, iron.App.h(), iron.App.w(), -iron.App.h()); + else + img2D.g2.drawImage(camera.renderTarget, 0, 0); - img.g2.color = Color.White; - - if (kha.Image.renderTargetsInvertedY()) { - img.g2.drawScaledImage(camera.renderTarget, 0, iron.App.h(), iron.App.w(), -iron.App.h()); - } else { - img.g2.drawImage(camera.renderTarget, 0, 0); - } - - if (inputs[15].get()) { - for (f in @:privateAccess iron.App.traitRenders2D) { - f(img.g2); - } - } - - img.g2.end(); + if (inputs[15].get()) + for (f in @:privateAccess iron.App.traitRenders2D) + f(img2D.g2); + img2D.g2.end(); } camera.renderTarget = oldRT; iron.Scene.active.camera = sceneCam; - tree.removeRender(render); + RenderPath.active.paused = rpPaused; + + if (!inputs[16].get()) + tree.removeRender(render); } diff --git a/leenkx/Sources/leenkx/logicnode/DrawRoundedRectNode.hx b/leenkx/Sources/leenkx/logicnode/DrawRoundedRectNode.hx index 86ba8da9..eef92052 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawRoundedRectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawRoundedRectNode.hx @@ -16,8 +16,8 @@ class DrawRoundedRectNode extends LogicNode { super(tree); } + #if lnx_ui override function run(from: Int) { - #if lnx_ui RenderToTexture.ensure2DContext("DrawPolygonNode"); final anchorH: Int = inputs[4].get(); @@ -61,8 +61,6 @@ class DrawRoundedRectNode extends LogicNode { } else { RenderToTexture.g.drawPolygon(0, 0, vertices, inputs[3].get()); } - #end - RenderToTexture.g.rotate(-angle, x, y); runOutput(0); } @@ -115,4 +113,5 @@ class DrawRoundedRectNode extends LogicNode { return vertices; } + #end } diff --git a/leenkx/Sources/leenkx/logicnode/DrawStringNode.hx b/leenkx/Sources/leenkx/logicnode/DrawStringNode.hx index 157b0ea1..bbf997df 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawStringNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawStringNode.hx @@ -3,9 +3,13 @@ package leenkx.logicnode; import kha.Font; import kha.Color; import leenkx.renderpath.RenderToTexture; +import kha.graphics2.VerTextAlignment; +import kha.graphics2.HorTextAlignment; #if lnx_ui import leenkx.ui.Canvas; + +using zui.GraphicsExtension; #end class DrawStringNode extends LogicNode { @@ -13,13 +17,20 @@ class DrawStringNode extends LogicNode { var lastFontName = ""; var string:String; + public var property1: String; + public var property2: String; + public function new(tree: LogicTree) { super(tree); } + #if lnx_ui override function run(from: Int) { RenderToTexture.ensure2DContext("DrawStringNode"); + var horA = TextLeft; + var verA = TextTop; + string = Std.string(inputs[1].get()); var angle: Float = inputs[7].get(); @@ -45,6 +56,18 @@ class DrawStringNode extends LogicNode { return; } + switch(property1){ + case 'TextLeft': horA = TextLeft; + case 'TextCenter': horA = TextCenter; + case 'TextRight': horA = TextRight; + } + + switch(property2){ + case 'TextTop': verA = TextTop; + case 'TextMiddle': verA = TextMiddle; + case 'TextBottom': verA = TextBottom; + } + RenderToTexture.g.rotate(angle, inputs[5].get(), inputs[6].get()); final colorVec = inputs[4].get(); @@ -53,7 +76,7 @@ class DrawStringNode extends LogicNode { RenderToTexture.g.fontSize = inputs[3].get(); RenderToTexture.g.font = font; - RenderToTexture.g.drawString(string, inputs[5].get(), inputs[6].get()); + RenderToTexture.g.drawAlignedString(string, inputs[5].get(), inputs[6].get(), horA, verA); RenderToTexture.g.rotate(-angle, inputs[5].get(), inputs[6].get()); @@ -65,4 +88,5 @@ class DrawStringNode extends LogicNode { return from == 1 ? RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string) : RenderToTexture.g.font.height(RenderToTexture.g.fontSize); } + #end } diff --git a/leenkx/Sources/leenkx/logicnode/FollowCurveNode.hx b/leenkx/Sources/leenkx/logicnode/FollowCurveNode.hx new file mode 100644 index 00000000..d607b7ed --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/FollowCurveNode.hx @@ -0,0 +1,47 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.CurveObject; +import iron.system.Time; + +class FollowCurveNode extends LogicNode { + + var progress: Float = -1; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var cycle: Bool = false; + var object: Object = inputs[1].get(); + var curve: CurveObject = inputs[2].get(); + var splineIdx: Int = inputs[3].get(); + + if (progress == -1) progress = inputs[8].get(); + + var len = curve.getLength(splineIdx); + var speed = inputs[5].get(); + + var currentDist = progress * len; + currentDist += (speed * Time.delta * (inputs[6].get() ? 1.0 : -1.0)); + + if (inputs[7].get()){ + if (currentDist > len){ currentDist -= len; cycle = true; } + else if (currentDist < 0){ currentDist += len; cycle = true; } + } + + progress = (len > 0) ? currentDist / len : 0.0; + + if (cycle) + runOutput(1); + else{ + curve.follow(object, progress, splineIdx, inputs[4].get()); + runOutput(0); + } + } + + override function get(from: Int): Dynamic { + return progress; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/FormatNumberNode.hx b/leenkx/Sources/leenkx/logicnode/FormatNumberNode.hx new file mode 100644 index 00000000..f83382bb --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/FormatNumberNode.hx @@ -0,0 +1,57 @@ +package leenkx.logicnode; + +class FormatNumberNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var number: Float = inputs[0].get(); + var thousandsSeparator: String = inputs[1].get(); + var decimalSeparator: String = inputs[2].get(); + var includeSymbol: Bool = inputs[3].get(); + + var s = Std.string(number); + var isNegative = s.charAt(0) == "-"; + if (isNegative) s = s.substring(1); + + var parts = s.split("."); + var integerPart = parts[0]; + var decimalPart = (parts.length > 1) ? parts[1] : ""; + + var len = integerPart.length; + var formattedInteger = ""; + + if (len <= 3) { + formattedInteger = integerPart; + } else { + var firstGroupLen = len % 3; + if (firstGroupLen == 0) firstGroupLen = 3; + + formattedInteger += integerPart.substring(0, firstGroupLen); + + var i = firstGroupLen; + while (i < len) { + formattedInteger += thousandsSeparator + integerPart.substring(i, i + 3); + i += 3; + } + } + + var finalNumber = formattedInteger; + if (decimalPart != "") { + finalNumber += decimalSeparator + decimalPart; + } + + var result = ""; + var sign = isNegative ? "-" : ""; + + if (includeSymbol) { + result = sign + "$" + finalNumber; + } else { + result = sign + finalNumber; + } + + return result; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/FormatTimeNode.hx b/leenkx/Sources/leenkx/logicnode/FormatTimeNode.hx new file mode 100644 index 00000000..e4ac2a5f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/FormatTimeNode.hx @@ -0,0 +1,32 @@ +package leenkx.logicnode; + +class FormatTimeNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var time: Float = inputs[0].get(); + var format: String = inputs[1].get(); + + var totalSeconds = Math.floor(time); + var milliseconds = Math.floor((time - totalSeconds) * 1000); + var centiseconds = Math.floor(milliseconds / 10); + + var h = Math.floor(totalSeconds / 3600); + var m = Math.floor((totalSeconds % 3600) / 60); + var s = totalSeconds % 60; + + var result = format; + result = StringTools.replace(result, "HH", (h < 10 ? "0" : "") + h); + result = StringTools.replace(result, "MM", (m < 10 ? "0" : "") + m); + result = StringTools.replace(result, "SS", (s < 10 ? "0" : "") + s); + result = StringTools.replace(result, "MS", (centiseconds < 10 ? "0" : "") + centiseconds); + result = StringTools.replace(result, "H", "" + h); + result = StringTools.replace(result, "M", "" + m); + result = StringTools.replace(result, "S", "" + s); + + return result; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetAgentDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetAgentDataNode.hx index b0a51836..610bc1c1 100644 --- a/leenkx/Sources/leenkx/logicnode/GetAgentDataNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetAgentDataNode.hx @@ -2,24 +2,32 @@ package leenkx.logicnode; import iron.object.Object; +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavAgent; +#end + class GetAgentDataNode extends LogicNode { public function new(tree: LogicTree) { super(tree); } - override function get(from: Int): Float { + override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); - assert(Error, object != null, "The object to naviagte should not be null"); - -#if lnx_navigation - var agent: leenkx.trait.NavAgent = object.getTrait(leenkx.trait.NavAgent); - assert(Error, agent != null, "The object does not have NavAgent Trait"); - if(from == 0) return agent.speed; - else return agent.turnDuration; -#else - return null; -#end + #if lnx_navigation + var agent: NavAgent = object.getTrait(NavAgent); + if (agent == null) return null; + return switch(from){ + case 0: agent.navMeshId; + case 1: agent.speed; + case 2: agent.turnDuration; + case 3: @:privateAccess agent.path; + default: null; + } + #else + return null; + #end } } \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/GetAreaLightDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetAreaLightDataNode.hx new file mode 100644 index 00000000..8c98424d --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetAreaLightDataNode.hx @@ -0,0 +1,27 @@ +package leenkx.logicnode; + +import iron.object.LightObject; + +class GetAreaLightDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var light: LightObject = inputs[0].get(); + + if (light == null) return null; + + #if lnx_ltc + if (light.data.raw.type == "area") + if (from == 0) + return light.data.raw.size; + else + return light.data.raw.size_y; + #end + + return null; + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetAssetsNode.hx b/leenkx/Sources/leenkx/logicnode/GetAssetsNode.hx new file mode 100644 index 00000000..db9c21f7 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetAssetsNode.hx @@ -0,0 +1,12 @@ +package leenkx.logicnode; + +class GetAssetsNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + return leenkx.system.Starter.assets; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetChildNode.hx b/leenkx/Sources/leenkx/logicnode/GetChildNode.hx index 958104c3..a85e4292 100644 --- a/leenkx/Sources/leenkx/logicnode/GetChildNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetChildNode.hx @@ -1,10 +1,18 @@ package leenkx.logicnode; import iron.object.Object; +import iron.object.MeshObject; +import iron.object.CameraObject; +import iron.object.LightObject; +import iron.object.SpeakerObject; +import iron.object.DecalObject; +import iron.object.ProbeObject; +import iron.object.CurveObject; class GetChildNode extends LogicNode { public var property0: String; + public var property1: String; public function new(tree: LogicTree) { super(tree); @@ -12,22 +20,31 @@ class GetChildNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); - var childName: String = inputs[1].get(); + if (object == null) return null; - if (object == null || childName == null) return null; + if (property0 != "By Type") { + var childName: String = inputs[1].get(); + if (childName == null) return null; - switch (property0) { - case "By Name": - return object.getChild(childName); - case "Contains": - return contains(object, childName); - case "Starts With": - return startsWith(object, childName); - case "Ends With": - return endsWith(object, childName); + return switch (property0) { + case "By Name": object.getChild(childName); + case "Contains": contains(object, childName); + case "Starts With": startsWith(object, childName); + case "Ends With": endsWith(object, childName); + default: null; + } } - return null; + return switch (property1) { + case "MeshObject": object.getChildOfType(MeshObject); + case "CameraObject": object.getChildOfType(CameraObject); + case "LightObject": object.getChildOfType(LightObject); + case "SpeakerObject": object.getChildOfType(SpeakerObject); + case "DecalObject": object.getChildOfType(DecalObject); + case "ProbeObject": object.getChildOfType(ProbeObject); + case "CurveObject": object.getChildOfType(CurveObject); + default: null; + } } function contains(o: Object, name: String): Object { diff --git a/leenkx/Sources/leenkx/logicnode/GetChildrenNode.hx b/leenkx/Sources/leenkx/logicnode/GetChildrenNode.hx index 492c2331..794db48c 100644 --- a/leenkx/Sources/leenkx/logicnode/GetChildrenNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetChildrenNode.hx @@ -13,6 +13,6 @@ class GetChildrenNode extends LogicNode { if (object == null) return null; - return object.children; + return from == 0 ? object.children : object.children.length; } } diff --git a/leenkx/Sources/leenkx/logicnode/GetContactsNode.hx b/leenkx/Sources/leenkx/logicnode/GetContactsNode.hx index fb25f394..d58eee3b 100644 --- a/leenkx/Sources/leenkx/logicnode/GetContactsNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetContactsNode.hx @@ -16,7 +16,9 @@ class GetContactsNode extends LogicNode { #if lnx_physics var physics = leenkx.trait.physics.PhysicsWorld.active; - var rbs = physics.getContacts(object.getTrait(RigidBody)); + var rb = object.getTrait(RigidBody); + if (rb == null) return null; + var rbs = physics.getContacts(rb); var obs = []; if (rbs != null) for (rb in rbs) if (rb != null) obs.push(rb.object); diff --git a/leenkx/Sources/leenkx/logicnode/GetCrowdDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetCrowdDataNode.hx new file mode 100644 index 00000000..43ddb742 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetCrowdDataNode.hx @@ -0,0 +1,39 @@ +package leenkx.logicnode; + +import iron.object.Object; + +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavCrowd; +#end + +class GetCrowdDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + + #if lnx_navigation + var crowdAgent: NavCrowd = object.getTrait(NavCrowd); + if (crowdAgent == null) return null; + return switch(from){ + case 0: crowdAgent.navMeshId; + case 1: crowdAgent.crowdAgentVelocity(); + case 2: crowdAgent.crowdAgentMaxSpeed(); + case 3: crowdAgent.crowdAgentMaxAcceleration(); + case 4: crowdAgent.turnSpeed; + case 5: crowdAgent.crowdAgentPosition(); + case 6: crowdAgent.crowdAgentNextPath(); + case 7: @:privateAccess crowdAgent.agentID; + case 8: crowdAgent.crowdAgentPath(); + default: null; + } + #else + return null; + + #end + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetCurveDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetCurveDataNode.hx new file mode 100644 index 00000000..0f17b2f9 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetCurveDataNode.hx @@ -0,0 +1,33 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; +import iron.math.Vec4; + +class GetCurveDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var curve: CurveObject = inputs[0].get(); + if (curve == null) return null; + return + switch (from) { + case 0: + curve.splinesLength; + case 1: + curve.equidistantSamples; + case 2: + curve.visible; + case 3: + curve.data.strength; + case 4: + new Vec4(curve.data.color[0], curve.data.color[1], curve.data.color[2], curve.data.color[3]); + case 5: + curve.curveMesh; + default: + null; + } + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetCurveSplineNode.hx b/leenkx/Sources/leenkx/logicnode/GetCurveSplineNode.hx new file mode 100644 index 00000000..59ee190b --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetCurveSplineNode.hx @@ -0,0 +1,36 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; +import iron.math.Vec4; + +class GetCurveSplineNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var curve: CurveObject = inputs[0].get(); + var index: Int = inputs[1].get(); + + if (index > curve.splinesLength) return null; + + switch(from){ + case 0: + var points: Array = []; + var worldMat = curve.transform.world; + for (point in curve.data.splines[index].points){ + var p = new Vec4(point.co[0], point.co[1], point.co[2]); + p.applymat(worldMat); + points.push(p); + } + return points; + case 1: + return curve.data.splines[index].closed; + case 2: + return curve.data.splines[index].resolution; + } + + return null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetFirstContactNode.hx b/leenkx/Sources/leenkx/logicnode/GetFirstContactNode.hx index b8339c57..7dd93ba8 100644 --- a/leenkx/Sources/leenkx/logicnode/GetFirstContactNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetFirstContactNode.hx @@ -15,7 +15,10 @@ class GetFirstContactNode extends LogicNode { #if lnx_physics var physics = leenkx.trait.physics.PhysicsWorld.active; - var rbs = physics.getContacts(object.getTrait(RigidBody)); + + var rb = object.getTrait(RigidBody); + if (rb == null) return null; + var rbs = physics.getContacts(rb); if (rbs != null && rbs.length > 0) return rbs[0].object; #end diff --git a/leenkx/Sources/leenkx/logicnode/GetGroupNode.hx b/leenkx/Sources/leenkx/logicnode/GetGroupNode.hx index 41c170e4..df066ad9 100644 --- a/leenkx/Sources/leenkx/logicnode/GetGroupNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetGroupNode.hx @@ -8,6 +8,6 @@ class GetGroupNode extends LogicNode { override function get(from: Int): Dynamic { var groupName: String = inputs[0].get(); - return iron.Scene.active.getGroup(groupName); + return from == 0 ? iron.Scene.active.getGroup(groupName) : iron.Scene.active.getGroup(groupName).length; } } diff --git a/leenkx/Sources/leenkx/logicnode/GetImageColorNode.hx b/leenkx/Sources/leenkx/logicnode/GetImageColorNode.hx index 6d8064b1..2a14d452 100644 --- a/leenkx/Sources/leenkx/logicnode/GetImageColorNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetImageColorNode.hx @@ -38,10 +38,10 @@ class GetImageColorNode extends LogicNode { renderTarget.g2.color = Color.White; if (property0 == 'Render' || property0 == 'Render&Render2D'){ - if (leenkx.renderpath.RenderPathCreator.finalTarget != null){ - var img: Image = iron.RenderPath.active.renderTargets.get("buf").image; - renderTarget.g2.drawScaledImage(img, 0, 0, iron.App.w(), iron.App.h()); - } + if (leenkx.renderpath.RenderPathCreator.finalTarget != null){ + var img: Image = iron.RenderPath.active.renderTargets.get("buf").image; + renderTarget.g2.drawScaledImage(img, 0, 0, iron.App.w(), iron.App.h()); + } } if (Image.renderTargetsInvertedY()){ @@ -75,7 +75,7 @@ class GetImageColorNode extends LogicNode { var pixels = renderTarget.getPixels(); var k = j * renderTarget.width + i; - + #if kha_krom var l = k; #elseif kha_html5 @@ -84,11 +84,11 @@ class GetImageColorNode extends LogicNode { var r = pixels.get(l * 4 + 0)/255; var g = pixels.get(l * 4 + 1)/255; - var b = pixels.get(l * 4 + 2)/255; + var b = pixels.get(l * 4 + 2)/255; var a = pixels.get(l * 4 + 3)/255; var v = new Vec4(r, g, b, a); - + return v; } diff --git a/leenkx/Sources/leenkx/logicnode/GetKeyboardNode.hx b/leenkx/Sources/leenkx/logicnode/GetKeyboardNode.hx new file mode 100644 index 00000000..0b77d517 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetKeyboardNode.hx @@ -0,0 +1,52 @@ +package leenkx.logicnode; + +import iron.system.Input; +import iron.system.Time; + +class GetKeyboardNode extends LogicNode { + + public var property0: String; + var activeKey: String = ""; + var lastTime: Float = -1.0; + + public function new(tree: LogicTree) { + super(tree); + tree.notifyOnUpdate(update); + } + + function update() { + var keyboard = Input.getKeyboard(); + var found = false; + + for (k in iron.system.Keyboard.keys) { + var b = false; + switch (property0) { + case "started": + b = keyboard.started(k); + case "down": + b = keyboard.down(k); + case "released": + b = keyboard.released(k); + } + + if (b) { + if (property0 == "started" || property0 == "released") { + var currentTime = Time.time(); + if (currentTime == lastTime && Time.delta != 0) continue; + lastTime = currentTime; + } + activeKey = k; + found = true; + break; + } + } + + if (found) { + runOutput(0); + } + } + + override function get(from: Int): Dynamic { + return activeKey; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetLightDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetLightDataNode.hx new file mode 100644 index 00000000..27fd0b38 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetLightDataNode.hx @@ -0,0 +1,37 @@ +package leenkx.logicnode; + +import iron.object.LightObject; + +class GetLightDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var light: LightObject = inputs[0].get(); + + if (light.data == null) return null; + + return switch (from) { + case 0: + return iron.data.LightData.typeToInt(light.data.raw.type); + #if lnx_single_point + case 1: + return light.data.raw.type == "sun" ? light.data.raw.strength / 0.325 : light.data.raw.strength / 0.01; + case 2: + return new iron.math.Vec4(light.data.raw.color[0], light.data.raw.color[1], light.data.raw.color[2]); + #else + case 1: + return light.data.raw.type == "sun" ? light.data.raw.strength / 0.325 : light.strength / 0.01; + case 2: + return light.data.raw.type == "sun" ? new iron.math.Vec4(light.data.raw.color[0], light.data.raw.color[1], light.data.raw.color[2]) : light.color; + #end + case 3: + light.data.raw.cast_shadow; + default: + null; + } + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetMaterialImageParamNode.hx b/leenkx/Sources/leenkx/logicnode/GetMaterialImageParamNode.hx new file mode 100644 index 00000000..b2ce1613 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetMaterialImageParamNode.hx @@ -0,0 +1,31 @@ +package leenkx.logicnode; + +import iron.Scene; +import iron.data.MaterialData; +import iron.object.Object; +import leenkx.trait.internal.UniformsManager; + +class GetMaterialImageParamNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + var perObject: Bool = inputs[1].get(); + var mat: MaterialData = inputs[2].get(); + var link: String = inputs[3].get(); + + if (mat == null || link == null) return null; + + if (!perObject) { + object = Scene.active.root; + } + + if (object == null) return null; + + var val = @:privateAccess UniformsManager.getObjectTextureLink(object, mat, link); + return val != null ? val : null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetMaterialRgbParamNode.hx b/leenkx/Sources/leenkx/logicnode/GetMaterialRgbParamNode.hx new file mode 100644 index 00000000..79925f1d --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetMaterialRgbParamNode.hx @@ -0,0 +1,31 @@ +package leenkx.logicnode; + +import iron.Scene; +import iron.data.MaterialData; +import iron.object.Object; +import leenkx.trait.internal.UniformsManager; + +class GetMaterialRgbParamNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + var perObject: Bool = inputs[1].get(); + var mat: MaterialData = inputs[2].get(); + var link: String = inputs[3].get(); + + if (mat == null || link == null) return null; + + if (!perObject) { + object = Scene.active.root; + } + + if (object == null) return null; + + var val = @:privateAccess UniformsManager.getObjectVec3Link(object, mat, link); + return val != null ? val : null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetMaterialValueParamNode.hx b/leenkx/Sources/leenkx/logicnode/GetMaterialValueParamNode.hx new file mode 100644 index 00000000..a0c3b24e --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetMaterialValueParamNode.hx @@ -0,0 +1,31 @@ +package leenkx.logicnode; + +import iron.Scene; +import iron.data.MaterialData; +import iron.object.Object; +import leenkx.trait.internal.UniformsManager; + +class GetMaterialValueParamNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + var perObject: Bool = inputs[1].get(); + var mat: MaterialData = inputs[2].get(); + var link: String = inputs[3].get(); + + if (mat == null || link == null) return 0.0; + + if (!perObject) { + object = Scene.active.root; + } + + if (object == null) return 0.0; + + var val = @:privateAccess UniformsManager.getObjectFloatLink(object, mat, link); + return val != null ? val : 0.0; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectByUidNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectByUidNode.hx index ca22e26e..82a07bfd 100644 --- a/leenkx/Sources/leenkx/logicnode/GetObjectByUidNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetObjectByUidNode.hx @@ -1,8 +1,5 @@ package leenkx.logicnode; -import iron.data.SceneFormat; -import iron.object.Object; - class GetObjectByUidNode extends LogicNode { public function new(tree: LogicTree) { diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectCameraDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectCameraDataNode.hx new file mode 100644 index 00000000..1ef1177d --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetObjectCameraDataNode.hx @@ -0,0 +1,27 @@ +package leenkx.logicnode; + +import iron.object.MeshObject; +import iron.Scene; + +class GetObjectCameraDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var mo: MeshObject = cast inputs[0].get(); + + if (mo == null) return null; + + if ((from == 0 && mo.cameraDistance == null) || (from == 1 && mo.screenSize == 0)) { + var cam = Scene.active.camera; + mo.computeCameraDistance(cam.transform.worldx(), cam.transform.worldy(), cam.transform.worldz()); + if (from == 1) { + mo.computeScreenSize(cam); + } + } + + return from == 0 ? mo.cameraDistance : mo.screenSize; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectGeomNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectGeomNode.hx index 307f4b36..bacc22f0 100644 --- a/leenkx/Sources/leenkx/logicnode/GetObjectGeomNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetObjectGeomNode.hx @@ -5,6 +5,8 @@ import iron.math.Vec4; class GetObjectGeomNode extends LogicNode { + public var property0: String; + public function new(tree: LogicTree) { super(tree); } @@ -14,13 +16,30 @@ class GetObjectGeomNode extends LogicNode { if (object == null) return null; - if (from == 5) - return object.vertex_groups; + var worldMat = object.transform.world; + + if (from == 5) { + if (property0 == "Global") { + var vertexGroups = new Map>(); + for (key in object.vertexGroups.keys()) { + var vp = []; + for (p in object.vertexGroups.get(key)) { + var p = new Vec4(p.x, p.y, p.z, p.w); + p.applymat(worldMat); + vp.push(p); + } + vertexGroups.set(key, vp); + } + return vertexGroups; + } + else + return object.vertexGroups; + } if (from == 6){ var keys = []; - if (object.vertex_groups != null) - for (key in object.vertex_groups.keys()) + if (object.vertexGroups != null) + for (key in object.vertexGroups.keys()) keys.push(key); return keys; @@ -36,8 +55,21 @@ class GetObjectGeomNode extends LogicNode { var normals = object.data.geom.normals.values; for (i in 0...Std.int(positions.length/4)){ - pos.push(new Vec4(positions[i*4]*scl/32767, positions[i*4+1]*scl/32767, positions[i*4+2]*scl/32767, 1)); - nor.push(new Vec4(normals[i*2]/32767, normals[i*2+1]/32767, positions[i*4+3]/32767, 1)); + var p = new Vec4(positions[i*4]*scl/32767, positions[i*4+1]*scl/32767, positions[i*4+2]*scl/32767, 1); + var n = new Vec4(normals[i*2]/32767, normals[i*2+1]/32767, positions[i*4+3]/32767, 1); + + if (property0 == "Global") { + p.applymat(worldMat); + var m = worldMat.clone(); + m.getInverse(m); + m.transpose3x3(); + m._30 = 0; m._31 = 0; m._32 = 0; + n.applymat(m); + n.normalize(); + } + + pos.push(p); + nor.push(n); } if (from == 0) diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectInstancedNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectInstancedNode.hx new file mode 100644 index 00000000..96cc2483 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetObjectInstancedNode.hx @@ -0,0 +1,43 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.MeshObject; +import kha.arrays.Float32Array; + +class GetObjectInstancedNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic{ + var object: Object =inputs[0].get(); + + if (object == null) return null; + + var geom = cast(object, MeshObject).data.geom; + + return + switch(from){ + case 0: geom.instanced; + case 1: @:privateAccess geom.data.raw.instanced_type; + case 2: geom.instanceCount; + case 3: geom.instanceStride; + case 4: toArray(@:privateAccess geom.data.raw.instanced_data); + default: null; + } + + } + + public static function toArray(elements: Float32Array): Array{ + if (elements == null) return null; + var len = elements.length; + var nid: Array = []; + + for (i in 0...len) { + nid[i] = elements[i]; + } + + return nid; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectNode.hx index fcb45150..63251a83 100644 --- a/leenkx/Sources/leenkx/logicnode/GetObjectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetObjectNode.hx @@ -1,13 +1,7 @@ package leenkx.logicnode; -import iron.data.SceneFormat; -import iron.object.Object; - class GetObjectNode extends LogicNode { - /** Scene from which to take the object **/ - public var property0: Null; - public function new(tree: LogicTree) { super(tree); } diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectShapeKeyNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectShapeKeyNode.hx new file mode 100644 index 00000000..7c62d8b3 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetObjectShapeKeyNode.hx @@ -0,0 +1,28 @@ +package leenkx.logicnode; + +import iron.object.MeshObject; + +class GetObjectShapeKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + #if lnx_morph_target + var object: Dynamic = inputs[0].get(); + var shapeKey: String = inputs[1].get(); + + assert(Error, object != null, "Object should not be null"); + var morph = cast(object, MeshObject).morphTarget; + + assert(Error, morph != null, "Object does not have shape keys"); + + if (morph.morphMap.exists(shapeKey)) + return morph.morphWeights.get(morph.morphMap.get(shapeKey)); + #end + return 0.0; + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetObjectTraitsNode.hx b/leenkx/Sources/leenkx/logicnode/GetObjectTraitsNode.hx index f9cc650e..b0f5b9fb 100644 --- a/leenkx/Sources/leenkx/logicnode/GetObjectTraitsNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetObjectTraitsNode.hx @@ -11,6 +11,8 @@ class GetObjectTraitsNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); if (object == null) return null; - return object.traits; + return + from == 0 ? object.name != null ? object.traits : inputs[0].get().root.children[0].traits : + object.name != null ? object.traits.length : inputs[0].get().root.children[0].traits.length; } } diff --git a/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx index 50698252..8531dde7 100644 --- a/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx @@ -62,6 +62,8 @@ class GetParticleDataNode extends LogicNode { @:privateAccess psys.lapTime; case 18: @:privateAccess psys.count; + case 19: + cast(mo.particleChildren[slot], Object); default: null; } diff --git a/leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx b/leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx deleted file mode 100644 index 66ced5f1..00000000 --- a/leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx +++ /dev/null @@ -1,33 +0,0 @@ -package leenkx.logicnode; - -#if lnx_audio -import iron.object.SpeakerObject; -import kha.audio1.AudioChannel; -#end - -class GetPositionSpeakerNode extends LogicNode { - - public function new(tree: LogicTree) { - super(tree); - } - - override function get(from: Int): Dynamic { - #if lnx_audio - var object: SpeakerObject = cast(inputs[0].get(), SpeakerObject); - if (object == null || object.sound == null) return 0.0; - - if (object.channels.length == 0) return 0.0; - - var channel = object.channels[0]; - - var position = 0.0; - if (channel != null) { - position = @:privateAccess channel.get_position(); - } - - return position; - #else - return 0.0; - #end - } -} diff --git a/leenkx/Sources/leenkx/logicnode/GetRigidBodyDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetRigidBodyDataNode.hx index 6cb4dbf9..904b001e 100644 --- a/leenkx/Sources/leenkx/logicnode/GetRigidBodyDataNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetRigidBodyDataNode.hx @@ -1,6 +1,7 @@ package leenkx.logicnode; import iron.object.Object; +import iron.math.Vec4; import leenkx.trait.physics.RigidBody; class GetRigidBodyDataNode extends LogicNode { @@ -16,33 +17,30 @@ class GetRigidBodyDataNode extends LogicNode { #if lnx_physics var rb = object.getTrait(RigidBody); + if (rb == null) return null; return switch (from) { - case 0: - rb == null ? return false : return true; - case 1: - rb.group; - case 2: - rb.mask; - case 3: - rb.animated; - case 4: - rb.staticObj; - case 5: - rb.angularDamping; - case 6: - rb.linearDamping; - case 7: - rb.friction; - case 8: - rb.mass; - //case 9: ; // collision shape - //case 10: ; // activation state - //case 11: ; // is gravity enabled - //case 12: ; // angular factor - //case 13: ; // linear factor - default: - null; + case 0: true; + case 1: rb.group; + case 2: rb.mask; + case 3: rb.animated; + case 4: rb.staticObj; + case 5: rb.linearDamping; + case 6: rb.angularDamping; + case 7: rb.friction; + case 8: rb.angularFriction; + case 9: rb.mass; + case 10: (rb.body.getCollisionFlags() & 4) != 0; + case 11: rb.restitution; + case 12: rb.getGravity().length() > 0.0001; + case 13: rb.getGravity(); + case 14: + var lf = @:privateAccess rb.linearFactors; + return new Vec4(lf[0], lf[1], lf[2]); + case 15: + var af = @:privateAccess rb.angularFactors; + return new Vec4(af[0], af[1], af[2]); + default: null; } #end diff --git a/leenkx/Sources/leenkx/logicnode/GetSoftBodyDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetSoftBodyDataNode.hx new file mode 100644 index 00000000..45f32764 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetSoftBodyDataNode.hx @@ -0,0 +1,47 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; +#if lnx_physics_soft +import leenkx.trait.physics.bullet.SoftBody; +#end + +class GetSoftBodyDataNode extends LogicNode { + + var friction: Float; + var damping: Float; + var pressure: Float; + var linearStiffness: Float; + var angularStiffness: Float; + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var obj: Object = inputs[0].get(); + if (obj == null) return null; + + #if lnx_physics_soft + var sb = obj.getTrait(SoftBody); + if (sb == null) return null; + + return switch (from) { + case 0: true; // Has SB + case 1: sb.mass; + case 2: sb.bend; + case 3: sb.shape; + case 4: sb.margin; + case 5: new Vec4(sb.vertOffsetX, sb.vertOffsetY, sb.vertOffsetZ); + case 6: sb.friction; + case 7: sb.damping; + case 8: sb.pressure; + case 9: sb.linearStiffness; + case 10: sb.angularStiffness; + default: null; + } + #end + + return null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetSpeakerDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetSpeakerDataNode.hx new file mode 100644 index 00000000..bae5dc37 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetSpeakerDataNode.hx @@ -0,0 +1,48 @@ +package leenkx.logicnode; + +#if lnx_audio +import iron.object.SpeakerObject; +import kha.audio1.AudioChannel; +#end + +class GetSpeakerDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + #if lnx_audio + var object: SpeakerObject = cast(inputs[0].get(), SpeakerObject); + + switch(from){ + case 0: + return object.volume; + case 1: + return object.data.pitch; + case 2: + return object.channels.length; + case 3: + var len: Array = null; + if (object.channels.length > 0){ + len = []; + for (channel in object.channels) + len.push(channel.length); + } + return len; + case 4: + var pos: Array = null; + if (object.channels.length > 0){ + pos = []; + for (channel in object.channels) + pos.push(channel.position); + } + return pos; + default: + return null; + } + #else + return null; + #end + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetSpotLightDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetSpotLightDataNode.hx new file mode 100644 index 00000000..e64a7c1a --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetSpotLightDataNode.hx @@ -0,0 +1,35 @@ +package leenkx.logicnode; + +import iron.object.LightObject; + +class GetSpotLightDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var light: LightObject = inputs[0].get(); + + if (light == null) return null; + + #if lnx_spot + if (light.data.raw.type == "spot"){ + #if lnx_single_point + if (from == 0) + return Math.acos(light.data.raw.spot_size) * 2; + else + return light.data.raw.spot_blend * 10; + #else + if (from == 0) + return Math.acos(light.size) * 2; + else + return light.blend * 10; + #end + } + #end + + return null; + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetTilesheetFlipNode.hx b/leenkx/Sources/leenkx/logicnode/GetTilesheetFlipNode.hx index cd9ddb88..4ca5bbff 100644 --- a/leenkx/Sources/leenkx/logicnode/GetTilesheetFlipNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetTilesheetFlipNode.hx @@ -12,9 +12,9 @@ class GetTilesheetFlipNode extends LogicNode { var object: MeshObject = inputs[0].get(); if (object == null) return null; - if (object.tilesheet == null) return null; - if (from == 0) return object.tilesheet.flipX; - if (from == 1) return object.tilesheet.flipY; + if (object.activeTilesheet == null) return null; + if (from == 0) return object.activeTilesheet.flipX; + if (from == 1) return object.activeTilesheet.flipY; return null; } diff --git a/leenkx/Sources/leenkx/logicnode/GetTilesheetStateNode.hx b/leenkx/Sources/leenkx/logicnode/GetTilesheetStateNode.hx index 55c0aab7..29300b9d 100644 --- a/leenkx/Sources/leenkx/logicnode/GetTilesheetStateNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetTilesheetStateNode.hx @@ -11,9 +11,9 @@ class GetTilesheetStateNode extends LogicNode { override function get(from: Int): Dynamic { var object: MeshObject = inputs[0].get(); - if (object == null || object.tilesheet == null) return null; + if (object == null || object.activeTilesheet == null) return null; - var tilesheet = object.tilesheet; + var tilesheet = object.activeTilesheet; return switch (from) { case 0: object.name; // Return object name since tilesheet is embedded diff --git a/leenkx/Sources/leenkx/logicnode/GetTraitNode.hx b/leenkx/Sources/leenkx/logicnode/GetTraitNode.hx index e1ef4794..bbd59e5b 100644 --- a/leenkx/Sources/leenkx/logicnode/GetTraitNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetTraitNode.hx @@ -17,6 +17,7 @@ class GetTraitNode extends LogicNode { if (object == null) return null; if (cname == null) cname = cast Type.resolveClass(Main.projectPackage + "." + name); if (cname == null) cname = cast Type.resolveClass(Main.projectPackage + ".node." + name); + if (cname == null) cname = cast Type.resolveClass(name); return object.getTrait(cname); } diff --git a/leenkx/Sources/leenkx/logicnode/GetTransformNode.hx b/leenkx/Sources/leenkx/logicnode/GetTransformNode.hx index 19230dcc..ad695b7d 100644 --- a/leenkx/Sources/leenkx/logicnode/GetTransformNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetTransformNode.hx @@ -10,9 +10,10 @@ class GetTransformNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); + var relative: Bool = inputs[1].get(); if (object == null) return null; - return object.transform.world; + return relative && object.parent != null ? object.transform.local : object.transform.world; } } diff --git a/leenkx/Sources/leenkx/logicnode/GetWorldDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetWorldDataNode.hx new file mode 100644 index 00000000..c9793d7f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetWorldDataNode.hx @@ -0,0 +1,34 @@ +package leenkx.logicnode; + +class GetWorldDataNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + + var world = iron.Scene.active.world.raw; + + return switch (from) { + case 0: + world.name; + case 1: + world.turbidity != null ? world.probe.strength * 10 : world.probe.strength; + case 2: + intToColor(world.background_color); + default: + null; + } + return null; + + } + + function intToColor(val: Int): Array { + var a: Float = ((val >>> 24) & 0xff) / 255.0; + var r: Float = ((val >> 16) & 0xff) / 255.0; + var g: Float = ((val >> 8) & 0xff) / 255.0; + var b: Float = (val & 0xff) / 255.0; + return [r, g, b, a]; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetWorldStrengthNode.hx b/leenkx/Sources/leenkx/logicnode/GetWorldStrengthNode.hx index cf3af0ba..af82f072 100644 --- a/leenkx/Sources/leenkx/logicnode/GetWorldStrengthNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GetWorldStrengthNode.hx @@ -7,6 +7,7 @@ class GetWorldStrengthNode extends LogicNode { } override function get(from: Int): Dynamic { - return iron.Scene.active.world.raw.probe.strength; + var world = iron.Scene.active.world.raw; + return world.turbidity != null ? world.probe.strength * 10 : world.probe.strength; } } \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx index 9d2e05ba..231c0566 100644 --- a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx @@ -1,82 +1,48 @@ package leenkx.logicnode; -#if lnx_physics -import leenkx.trait.physics.PhysicsWorld; -#end +#if lnx_navigation import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +import leenkx.trait.NavAgent; +#end + import iron.object.Object; import iron.math.Vec4; class GoToLocationNode extends LogicNode { - var object: Object; - var location: Vec4; - var speed: Float; - var turnDuration: Float; - var heightOffset: Float; - var useRaycast: Bool; - var rayCastDepth: Float; - var rayCastMask: Int; - public function new(tree: LogicTree) { super(tree); } override function run(from: Int) { - object = inputs[1].get(); - location = inputs[2].get(); - speed = inputs[3].get(); - turnDuration = inputs[4].get(); - heightOffset = inputs[5].get(); - useRaycast = inputs[6].get(); - rayCastDepth = inputs[7].get(); - rayCastMask = inputs[8].get(); - - assert(Error, object != null, "The object input not be null"); - assert(Error, location != null, "The location to navigate to must not be null"); - assert(Error, speed != null, "Speed of Nav Agent should not be null"); + + var navMeshId: String = inputs[1].get(); + var object: Object = inputs[2].get(); + var location: Vec4 = inputs[3].get(); + var speed: Float = inputs[4].get(); + var turnDuration: Float = inputs[5].get(); + var heightOffset: Float = inputs[6].get(); + assert(Warning, speed >= 0, "Speed of Nav Agent should be positive"); - assert(Error, turnDuration != null, "Turn Duration of Nav Agent should not be null"); assert(Warning, turnDuration >= 0, "Turn Duration of Nav Agent should be positive"); -#if lnx_navigation - var from = object.transform.world.getLoc(); - var to = location; + #if lnx_navigation + var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(navMeshId); + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); - assert(Error, Navigation.active.navMeshes.length > 0, "No Navigation Mesh Present"); - Navigation.active.navMeshes[0].findPath(from, to, function(path: Array) { - var agent: leenkx.trait.NavAgent = object.getTrait(leenkx.trait.NavAgent); - assert(Error, agent != null, "Object does not have a NavAgent trait"); - agent.speed = speed; - agent.turnDuration = turnDuration; - agent.heightOffset = heightOffset; - agent.tickPos = tickPos; - agent.tickRot = tickRot; - agent.setPath(path); - }); -#end + activeNavMesh.findPath(object.transform.world.getLoc(), location, function(path: Array) { + var agent: NavAgent = object.getTrait(NavAgent); + assert(Error, agent != null, "Object does not have a NavAgent trait"); + agent.navMeshId = navMeshId; + agent.speed = speed; + agent.turnDuration = turnDuration; + agent.heightOffset = heightOffset; + agent.setPath(path); + }); + #end runOutput(0); } - function tickPos(){ - #if lnx_physics - if(useRaycast) setAgentHeight(); - #end - runOutput(1); - } - - function tickRot(){ - runOutput(2); - } - - function setAgentHeight(){ - #if lnx_physics - var fromLoc = object.transform.world.getLoc(); - var toLoc = fromLoc.clone(); - toLoc.z += rayCastDepth; - var hit = PhysicsWorld.active.rayCast(fromLoc, toLoc, rayCastMask); - if(hit != null) object.transform.loc.z = hit.pos.z + heightOffset; - #end - } } diff --git a/leenkx/Sources/leenkx/logicnode/GroupNode.hx b/leenkx/Sources/leenkx/logicnode/GroupNode.hx index aee02c76..ce01d308 100644 --- a/leenkx/Sources/leenkx/logicnode/GroupNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GroupNode.hx @@ -11,6 +11,6 @@ class GroupNode extends LogicNode { } override function get(from: Int): Dynamic { - return Scene.active.getGroup(property0); + return from == 0 ? Scene.active.getGroup(property0) : Scene.active.getGroup(property0).length; } } diff --git a/leenkx/Sources/leenkx/logicnode/HasContactArrayNode.hx b/leenkx/Sources/leenkx/logicnode/HasContactArrayNode.hx index 1fad6edb..af4144c2 100644 --- a/leenkx/Sources/leenkx/logicnode/HasContactArrayNode.hx +++ b/leenkx/Sources/leenkx/logicnode/HasContactArrayNode.hx @@ -17,14 +17,16 @@ class HasContactArrayNode extends LogicNode { #if lnx_physics var physics = leenkx.trait.physics.PhysicsWorld.active; var rb1 = object1.getTrait(RigidBody); - var rbs = physics.getContacts(rb1); - - if (rb1 != null && rbs != null) { - for (object2 in objects) { - var rb2 = object2.getTrait(RigidBody); - for (rb in rbs) { - if (rb == rb2) { - return true; + if (rb1 != null){ + var rbs = physics.getContacts(rb1); + + if (rbs != null) { + for (object2 in objects) { + var rb2 = object2.getTrait(RigidBody); + for (rb in rbs) { + if (rb == rb2) { + return true; + } } } } diff --git a/leenkx/Sources/leenkx/logicnode/LogicTree.hx b/leenkx/Sources/leenkx/logicnode/LogicTree.hx index 94fddab0..f3535bca 100644 --- a/leenkx/Sources/leenkx/logicnode/LogicTree.hx +++ b/leenkx/Sources/leenkx/logicnode/LogicTree.hx @@ -27,6 +27,12 @@ class LogicTree extends iron.Trait { public function add() {} + public override function remove() { + if (paused) + _remove = null; + super.remove(); + } + public var paused = false; public function pause() { @@ -36,6 +42,7 @@ class LogicTree extends iron.Trait { if (_fixedUpdate != null) for (f in _fixedUpdate) iron.App.removeFixedUpdate(f); if (_update != null) for (f in _update) iron.App.removeUpdate(f); if (_lateUpdate != null) for (f in _lateUpdate) iron.App.removeLateUpdate(f); + if (_render2D != null) for (f in _render2D) iron.App.removeRender2D(f); } public function resume() { @@ -45,5 +52,6 @@ class LogicTree extends iron.Trait { if (_fixedUpdate != null) for (f in _fixedUpdate) iron.App.notifyOnFixedUpdate(f); if (_update != null) for (f in _update) iron.App.notifyOnUpdate(f); if (_lateUpdate != null) for (f in _lateUpdate) iron.App.notifyOnLateUpdate(f); + if (_render2D != null) for (f in _render2D) iron.App.notifyOnRender2D(f); } } diff --git a/leenkx/Sources/leenkx/logicnode/LookAtNode.hx b/leenkx/Sources/leenkx/logicnode/LookAtNode.hx index c4a90261..a876b979 100644 --- a/leenkx/Sources/leenkx/logicnode/LookAtNode.hx +++ b/leenkx/Sources/leenkx/logicnode/LookAtNode.hx @@ -2,13 +2,20 @@ package leenkx.logicnode; import iron.math.Vec4; import iron.math.Quat; +import iron.math.Mat4; class LookAtNode extends LogicNode { public var property0: String; - var v1 = new Vec4(); - var v2 = new Vec4(); var q = new Quat(); + var m = Mat4.identity(); + var f = new Vec4(); + var r = new Vec4(); + var u = new Vec4(); + var z = new Vec4(); + var vx = new Vec4(); + var vy = new Vec4(); + var vz = new Vec4(); public function new(tree: LogicTree) { super(tree); @@ -18,25 +25,48 @@ class LookAtNode extends LogicNode { var vfrom: Vec4 = inputs[0].get(); var vto: Vec4 = inputs[1].get(); - if (vfrom == null || vto == null) return null; + f.setFrom(vto).sub(vfrom).normalize(); + z.set(0, 0, 1); + + if (Math.abs(f.dot(z)) > 0.9999) { + z.set(0, 1, 0); + } + + r.crossvecs(f, z).normalize(); + u.crossvecs(r, f).normalize(); switch (property0) { case "X": - v1.set(1, 0, 0); + vx.setFrom(f); + vy.set(-r.x, -r.y, -r.z); + vz.setFrom(u); case "-X": - v1.set(-1, 0, 0); + vx.set(-f.x, -f.y, -f.z); + vy.setFrom(r); + vz.setFrom(u); case "Y": - v1.set(0, 1, 0); + vx.setFrom(r); + vy.setFrom(f); + vz.setFrom(u); case "-Y": - v1.set(0, -1, 0); + vx.set(-r.x, -r.y, -r.z); + vy.set(-f.x, -f.y, -f.z); + vz.setFrom(u); case "Z": - v1.set(0, 0, 1); + vx.setFrom(r); + vy.set(-u.x, -u.y, -u.z); + vz.setFrom(f); case "-Z": - v1.set(0, 0, -1); + vx.setFrom(r); + vy.setFrom(u); + vz.set(-f.x, -f.y, -f.z); } - v2.setFrom(vto).sub(vfrom).normalize(); - q.fromTo(v1, v2); + m._00 = vx.x; m._01 = vx.y; m._02 = vx.z; + m._10 = vy.x; m._11 = vy.y; m._12 = vy.z; + m._20 = vz.x; m._21 = vz.y; m._22 = vz.z; + + q.fromRotationMat(m); return q; } } diff --git a/leenkx/Sources/leenkx/logicnode/MapRangeNode.hx b/leenkx/Sources/leenkx/logicnode/MapRangeNode.hx index a12a4f0f..3fe32904 100644 --- a/leenkx/Sources/leenkx/logicnode/MapRangeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MapRangeNode.hx @@ -6,17 +6,18 @@ class MapRangeNode extends LogicNode { super(tree); } - override function get(from: Int): kha.FastFloat { - var value = inputs[0].get(); - var fromMin = inputs[1].get(); - var fromMax = inputs[2].get(); - var toMin = inputs[3].get(); - var toMax = inputs[4].get(); + override function get(from: Int): Dynamic { + var value: Float = inputs[0].get(); + var fromMin: Float = inputs[1].get(); + var fromMax: Float = inputs[2].get(); + var toMin: Float = inputs[3].get(); + var toMax: Float = inputs[4].get(); - if (value == null || fromMin == null || fromMax == null || toMin == null || toMax == null) return null; + var deltaFrom = fromMax - fromMin; + if (deltaFrom == 0) return (toMin + toMax) / 2.0; - //Implements https://stackoverflow.com/a/5732390 - var slope = (toMax - toMin) / (fromMax - fromMin); + //https://stackoverflow.com/a/5732390 + var slope = (toMax - toMin) / deltaFrom; return toMin + slope * (value - fromMin); } } diff --git a/leenkx/Sources/leenkx/logicnode/MaterialNode.hx b/leenkx/Sources/leenkx/logicnode/MaterialNode.hx index ebbac913..b000ae37 100644 --- a/leenkx/Sources/leenkx/logicnode/MaterialNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MaterialNode.hx @@ -16,9 +16,43 @@ class MaterialNode extends LogicNode { } override function get(from: Int): Dynamic { - if (property0 != null) { - iron.data.Data.getMaterial(iron.Scene.active.raw.name, property0, function(mat: MaterialData) { - value = mat; + if (property0 != null && value == null) { + var currentSceneName = iron.Scene.active.raw.name; + + iron.data.Data.getSceneRaw(currentSceneName, function(currentFormat: iron.data.SceneFormat.TSceneFormat) { + var foundInCurrent = false; + + if (currentFormat.material_datas != null) { + for (matData in currentFormat.material_datas) { + if (matData.name == property0) { + foundInCurrent = true; + iron.data.Data.getMaterial(currentSceneName, property0, function(mat: MaterialData) { + value = mat; + }); + break; + } + } + } + + if (!foundInCurrent) { + for (sceneName in leenkx.system.Starter.scenes) { + if (sceneName == currentSceneName) continue; + + iron.data.Data.getSceneRaw(sceneName, function(sceneFormat: iron.data.SceneFormat.TSceneFormat) { + if (sceneFormat.material_datas != null) { + for (matData in sceneFormat.material_datas) { + if (matData.name == property0) { + iron.data.Data.getMaterial(sceneName, property0, function(mat: MaterialData) { + value = mat; + }); + break; + } + } + } + }); + if (value != null) break; + } + } }); } diff --git a/leenkx/Sources/leenkx/logicnode/MathConstantsNode.hx b/leenkx/Sources/leenkx/logicnode/MathConstantsNode.hx new file mode 100644 index 00000000..0bbd9010 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/MathConstantsNode.hx @@ -0,0 +1,16 @@ +package leenkx.logicnode; + +import iron.math.Vec4; + +class MathConstantsNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override public function get(index: Int): Dynamic { + if (index == 0) return Math.NEGATIVE_INFINITY; + else if (index == 1) return Math.POSITIVE_INFINITY; + else return Math.PI; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/MathNode.hx b/leenkx/Sources/leenkx/logicnode/MathNode.hx index a2c26c7f..66984fac 100644 --- a/leenkx/Sources/leenkx/logicnode/MathNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MathNode.hx @@ -98,7 +98,18 @@ class MathNode extends LogicNode { } case "Ping-Pong": r = pingpong(inputs[0].get(), inputs[1].get()); - } + case "Hyperbolic Sine": + var x = inputs[0].get(); + r = (Math.exp(x) - Math.exp(-x)) / 2; + case "Hyperbolic Cosine": + var x = inputs[0].get(); + r = (Math.exp(x) + Math.exp(-x)) / 2; + case "Hyperbolic Tangent": + var x = inputs[0].get(); + var sinh = (Math.exp(x) - Math.exp(-x)) / 2; + var cosh = (Math.exp(x) + Math.exp(-x)) / 2; + r = sinh / cosh; + } // Clamp if (property1) r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r); diff --git a/leenkx/Sources/leenkx/logicnode/MatrixMathNode.hx b/leenkx/Sources/leenkx/logicnode/MatrixMathNode.hx deleted file mode 100644 index ad528f7b..00000000 --- a/leenkx/Sources/leenkx/logicnode/MatrixMathNode.hx +++ /dev/null @@ -1,28 +0,0 @@ -package leenkx.logicnode; - -import iron.math.Mat4; - -class MatrixMathNode extends LogicNode { - - public var property0: String; - var m = Mat4.identity(); - - public function new(tree: LogicTree) { - super(tree); - } - - override function get(from: Int): Dynamic { - var m1: Mat4 = inputs[0].get(); - var m2: Mat4 = inputs[1].get(); - - if (m1 == null || m2 == null) return null; - - m.setFrom(m1); - switch (property0) { - case "Multiply": - m.multmat(m2); - } - - return m; - } -} diff --git a/leenkx/Sources/leenkx/logicnode/MergedKeyboardNode.hx b/leenkx/Sources/leenkx/logicnode/MergedKeyboardNode.hx index b95c6c57..68cb05a9 100644 --- a/leenkx/Sources/leenkx/logicnode/MergedKeyboardNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MergedKeyboardNode.hx @@ -1,9 +1,12 @@ package leenkx.logicnode; +import iron.system.Time; + class MergedKeyboardNode extends LogicNode { public var property0: String; public var property1: String; + var lastTime: Float = -1.0; public function new(tree: LogicTree) { super(tree); @@ -22,7 +25,14 @@ class MergedKeyboardNode extends LogicNode { case "released": b = keyboard.released(property1); } - if (b) runOutput(0); + if (b) { + if (property0 == "started" || property0 == "released") { + var currentTime = Time.time(); + if (currentTime == lastTime && Time.delta != 0) return; + lastTime = currentTime; + } + runOutput(0); + } } override function get(from: Int): Dynamic { diff --git a/leenkx/Sources/leenkx/logicnode/MergedMouseNode.hx b/leenkx/Sources/leenkx/logicnode/MergedMouseNode.hx index a03c134a..ad1865ce 100644 --- a/leenkx/Sources/leenkx/logicnode/MergedMouseNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MergedMouseNode.hx @@ -4,11 +4,14 @@ package leenkx.logicnode; import leenkx.trait.internal.DebugConsole; #end +import iron.system.Time; + class MergedMouseNode extends LogicNode { public var property0: String; public var property1: String; public var property2: Bool; + var lastTime: Float = -1.0; public function new(tree: LogicTree) { super(tree); @@ -33,7 +36,14 @@ class MergedMouseNode extends LogicNode { case "moved": b = mouse.moved; } - if (b) runOutput(0); + if (b) { + if (property0 == "started" || property0 == "released") { + var currentTime = Time.time(); + if (currentTime == lastTime && Time.delta != 0) return; + lastTime = currentTime; + } + runOutput(0); + } } override function get(from: Int): Dynamic { diff --git a/leenkx/Sources/leenkx/logicnode/MergedSurfaceNode.hx b/leenkx/Sources/leenkx/logicnode/MergedSurfaceNode.hx index c73a2fe8..29608f93 100644 --- a/leenkx/Sources/leenkx/logicnode/MergedSurfaceNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MergedSurfaceNode.hx @@ -1,8 +1,11 @@ package leenkx.logicnode; +import iron.system.Time; + class MergedSurfaceNode extends LogicNode { public var property0: String; + var lastTime: Float = -1.0; public function new(tree: LogicTree) { super(tree); @@ -23,7 +26,14 @@ class MergedSurfaceNode extends LogicNode { case "moved": b = surface.moved; } - if (b) runOutput(0); + if (b) { + if (property0 == "started" || property0 == "released") { + var currentTime = Time.time(); + if (currentTime == lastTime && Time.delta != 0) return; + lastTime = currentTime; + } + runOutput(0); + } } override function get(from: Int): Dynamic { diff --git a/leenkx/Sources/leenkx/logicnode/MergedVirtualButtonNode.hx b/leenkx/Sources/leenkx/logicnode/MergedVirtualButtonNode.hx index c7e83469..c0882fe9 100644 --- a/leenkx/Sources/leenkx/logicnode/MergedVirtualButtonNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MergedVirtualButtonNode.hx @@ -1,9 +1,12 @@ package leenkx.logicnode; +import iron.system.Time; + class MergedVirtualButtonNode extends LogicNode { public var property0: String; public var property1: String; + var lastTime: Float = -1.0; public function new(tree: LogicTree) { super(tree); @@ -23,7 +26,14 @@ class MergedVirtualButtonNode extends LogicNode { case "released": b = vb.released; } - if (b) runOutput(0); + if (b) { + if (property0 == "started" || property0 == "released") { + var currentTime = Time.time(); + if (currentTime == lastTime && Time.delta != 0) return; + lastTime = currentTime; + } + runOutput(0); + } } override function get(from: Int): Dynamic { diff --git a/leenkx/Sources/leenkx/logicnode/NavigableLocationNode.hx b/leenkx/Sources/leenkx/logicnode/NavigableLocationNode.hx index ebe21d33..9a50eee3 100644 --- a/leenkx/Sources/leenkx/logicnode/NavigableLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/NavigableLocationNode.hx @@ -1,25 +1,40 @@ package leenkx.logicnode; -import iron.math.Vec4; +#if lnx_navigation import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +#end + +import iron.object.Object; +import iron.math.Vec4; class NavigableLocationNode extends LogicNode { - var loc: Vec4; - public function new(tree: LogicTree) { super(tree); } override function get(from: Int): Dynamic { -#if lnx_navigation - - assert(Error, Navigation.active.navMeshes.length > 0, "No Navigation Mesh Present"); - Navigation.active.navMeshes[0].getRandomPoint(function(p: Vec4) { - loc = p; - }); -#end - return loc; + #if lnx_navigation + var object: Object = inputs[0].get(); + + var v = new iron.math.Vec4(); + + var centerLoc: Vec4 = object.transform.world.getLoc(); + var dim: Vec4 = object.transform.dim; + + var v = new Vec4( + centerLoc.x + dim.x * 0.5 * (Math.random() * 2.0 - 1.0), + centerLoc.y + dim.y * 0.5 * (Math.random() * 2.0 - 1.0), + centerLoc.z + dim.z * 0.5 * (Math.random() * 2.0 - 1.0) + ); + + var activeNavMesh: NavMesh = object.getTrait(NavMesh); + + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + return activeNavMesh.getClosestPoint(v); + #end + return null; } } diff --git a/leenkx/Sources/leenkx/logicnode/OnContactArrayNode.hx b/leenkx/Sources/leenkx/logicnode/OnContactArrayNode.hx index 05713146..c859f31e 100644 --- a/leenkx/Sources/leenkx/logicnode/OnContactArrayNode.hx +++ b/leenkx/Sources/leenkx/logicnode/OnContactArrayNode.hx @@ -7,6 +7,7 @@ class OnContactArrayNode extends LogicNode { public var property0: String; var lastContact = false; + var contactObjects: Array = []; public function new(tree: LogicTree) { super(tree); @@ -19,28 +20,30 @@ class OnContactArrayNode extends LogicNode { var objects: Array = inputs[1].get(); if (object1 == null) object1 = tree.object; - if (objects == null)return; + if (objects == null) return; - var contact = false; + var currentContacts: Array = []; #if lnx_physics var physics = leenkx.trait.physics.PhysicsWorld.active; var rb1 = object1.getTrait(RigidBody); - var rbs = physics.getContacts(rb1); - if (rb1 != null && rbs != null) { - for (object2 in objects) { - var rb2 = object2.getTrait(RigidBody); - for (rb in rbs) { - if (rb == rb2) { - contact = true; - break; + if (rb1 != null){ + var rbs = physics.getContacts(rb1); + if (rbs != null) { + for (object2 in objects) { + var rb2 = object2.getTrait(RigidBody); + for (rb in rbs) { + if (rb == rb2) { + currentContacts.push(object2); + break; + } } } - if (contact) break; } } #end + var contact = currentContacts.length > 0; var b = false; switch (property0) { case "begin": @@ -52,7 +55,13 @@ class OnContactArrayNode extends LogicNode { } lastContact = contact; + contactObjects = currentContacts; if (b) runOutput(0); } + + override function get(from: Int): Dynamic { + if (from == 1) return contactObjects; + return contactObjects.length > 0 ? contactObjects[0] : null; + } } diff --git a/leenkx/Sources/leenkx/logicnode/OnRender2DNode.hx b/leenkx/Sources/leenkx/logicnode/OnRender2DNode.hx index f4031385..1bb1eb79 100644 --- a/leenkx/Sources/leenkx/logicnode/OnRender2DNode.hx +++ b/leenkx/Sources/leenkx/logicnode/OnRender2DNode.hx @@ -6,7 +6,13 @@ class OnRender2DNode extends LogicNode { public function new(tree: LogicTree) { super(tree); - tree.notifyOnRender2D(onRender2D); + #if lnx_ui + tree.notifyOnInit(initNode); + #end + } + + function initNode() { + tree.notifyOnRender2D(onRender2D, inputs[0].get()); } function onRender2D(g: kha.graphics2.Graphics) { @@ -15,4 +21,8 @@ class OnRender2DNode extends LogicNode { runOutput(0); RenderToTexture.g = null; } + + override function get(from: Int): Dynamic { + return onRender2D; + } } diff --git a/leenkx/Sources/leenkx/logicnode/PauseActionNode.hx b/leenkx/Sources/leenkx/logicnode/PauseActionNode.hx index 8d58b2b0..aa88a573 100644 --- a/leenkx/Sources/leenkx/logicnode/PauseActionNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PauseActionNode.hx @@ -11,7 +11,7 @@ class PauseActionNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var animation = object.animation; if (animation == null) animation = object.getBoneAnimation(object.uid); diff --git a/leenkx/Sources/leenkx/logicnode/PauseSoundNode.hx b/leenkx/Sources/leenkx/logicnode/PauseSoundNode.hx index 6d699f59..ac9501e0 100644 --- a/leenkx/Sources/leenkx/logicnode/PauseSoundNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PauseSoundNode.hx @@ -11,7 +11,7 @@ class PauseSoundNode extends LogicNode { override function run(from: Int) { #if lnx_audio var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); - if (object == null) return; + if (object == null){ runOutput(0); return; } object.pause(); #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/PauseTilesheetNode.hx b/leenkx/Sources/leenkx/logicnode/PauseTilesheetNode.hx index ce1716b6..d9055775 100644 --- a/leenkx/Sources/leenkx/logicnode/PauseTilesheetNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PauseTilesheetNode.hx @@ -11,9 +11,10 @@ class PauseTilesheetNode extends LogicNode { override function run(from: Int) { var object: MeshObject = inputs[1].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; - object.tilesheet.pause(); + object.activeTilesheet.pause(); runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/PickLocationNode.hx b/leenkx/Sources/leenkx/logicnode/PickLocationNode.hx index 54a59d72..46261e30 100644 --- a/leenkx/Sources/leenkx/logicnode/PickLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PickLocationNode.hx @@ -1,33 +1,94 @@ package leenkx.logicnode; +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +#end + +#if lnx_physics +import leenkx.trait.physics.PhysicsWorld; +import leenkx.trait.physics.RigidBody; +#end + import iron.object.Object; import iron.math.Vec4; +import iron.math.RayCaster; +import iron.Scene; class PickLocationNode extends LogicNode { - var loc = new Vec4(); - public function new(tree: LogicTree) { super(tree); } override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); - var coords: Vec4 = inputs[1].get(); + var x: Int = inputs[1].get(); + var y: Int = inputs[2].get(); - if (object == null || coords == null) return null; + var loc = new Vec4(); -#if lnx_physics - var physics = leenkx.trait.physics.PhysicsWorld.active; - var b = physics.pickClosest(coords.x, coords.y); - var rb = object.getTrait(leenkx.trait.physics.RigidBody); + #if lnx_navigation + var activeNavMesh: NavMesh = object.getTrait(NavMesh); + var useChildren = activeNavMesh != null && activeNavMesh.combineImmidiateChildren; + #end - if (rb != null && b == rb) { - var p = physics.hitPointWorld; - loc.set(p.x, p.y, p.z); - return loc; + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null){ + var physics = PhysicsWorld.active; + var b = physics.pickClosest(x, y); + + var hit = false; + + if (b == rb) + hit = true; + + if (!hit) { + #if lnx_navigation + if (useChildren) { + for (child in object.children) { + if (child.raw.type != "mesh_object") continue; + + var childRb = child.getTrait(RigidBody); + if (childRb != null && childRb == b) { + hit = true; + break; + } + } + } + #end + } + + if (hit) { + var p = physics.hitPointWorld; + loc.set(p.x, p.y, p.z); + return loc; + } + else + return null; } -#end + #end + + #if lnx_navigation + + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + + loc = RayCaster.boxIntersectObject(object, x, y, Scene.active.camera); + + if (loc == null && useChildren) { + for (child in object.children) { + if (child.raw.type != "mesh_object") continue; + + loc = RayCaster.boxIntersectObject(child, x, y, Scene.active.camera); + + if (loc != null) break; + } + } + + return loc != null ? activeNavMesh.getClosestPoint(loc) : null; + #end + return null; } } diff --git a/leenkx/Sources/leenkx/logicnode/PlayActionFromNode.hx b/leenkx/Sources/leenkx/logicnode/PlayActionFromNode.hx index 9fae6be7..b1dfe8f3 100644 --- a/leenkx/Sources/leenkx/logicnode/PlayActionFromNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PlayActionFromNode.hx @@ -50,7 +50,7 @@ class PlayActionFromNode extends LogicNode { loop = inputs[7].get(); reverse = inputs[8].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } animation = object.animation; if (animation == null) animation = object.getBoneAnimation(object.uid); @@ -63,7 +63,7 @@ class PlayActionFromNode extends LogicNode { #if lnx_skin if (animation.isSkinned){ for(a in animation.armature.actions) - if (a.name == actionR) isnew = false; + if (a.name == actionR){ isnew = false; break; } if (isnew){ for(a in animation.armature.actions) @@ -156,7 +156,7 @@ class PlayActionFromNode extends LogicNode { var oactions = cast(animation, ObjectAnimation).oactions; for (a in oactions) - if (a != null && a.objects[0].name == actionR) isnew = false; + if (a != null && a.objects[0].name == actionR){ isnew = false; break; } if (isnew){ for (a in oactions){ diff --git a/leenkx/Sources/leenkx/logicnode/PlayActionNode.hx b/leenkx/Sources/leenkx/logicnode/PlayActionNode.hx index 34243596..625f47ee 100644 --- a/leenkx/Sources/leenkx/logicnode/PlayActionNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PlayActionNode.hx @@ -13,7 +13,7 @@ class PlayActionNode extends LogicNode { var action: String = inputs[2].get(); var blendTime: Float = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var animation = object.animation; if (animation == null) animation = object.getBoneAnimation(object.uid); diff --git a/leenkx/Sources/leenkx/logicnode/PlaySoundNode.hx b/leenkx/Sources/leenkx/logicnode/PlaySoundNode.hx index f07de6b8..ff7a55fe 100644 --- a/leenkx/Sources/leenkx/logicnode/PlaySoundNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PlaySoundNode.hx @@ -1,9 +1,14 @@ package leenkx.logicnode; #if lnx_audio import iron.object.SpeakerObject; +import kha.audio1.AudioChannel; #end class PlaySoundNode extends LogicNode { + #if lnx_audio + var channel: AudioChannel; + #end + public function new(tree: LogicTree) { super(tree); } @@ -11,9 +16,23 @@ class PlaySoundNode extends LogicNode { override function run(from: Int) { #if lnx_audio var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); - if (object == null) return; - object.play(); + if (object == null){ runOutput(0); return; } + + if (inputs[2].get()) object.stop(); + object.data.loop = inputs[3].get(); + object.data.stream = inputs[4].get(); + channel = object.play(); + if (channel != null) tree.notifyOnUpdate(this.onUpdate); #end runOutput(0); } + + #if lnx_audio + function onUpdate() { + if (channel != null && channel.finished){ + channel = null; + runOutput(1); + } + } + #end } diff --git a/leenkx/Sources/leenkx/logicnode/PlaySoundRawNode.hx b/leenkx/Sources/leenkx/logicnode/PlaySoundRawNode.hx index 68661d9e..077bde5d 100644 --- a/leenkx/Sources/leenkx/logicnode/PlaySoundRawNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PlaySoundRawNode.hx @@ -1,5 +1,12 @@ package leenkx.logicnode; +#if lnx_audio +import kha.Sound; +import kha.audio1.AudioChannel; +import iron.system.Audio; +import iron.data.Data; +#end + class PlaySoundRawNode extends LogicNode { /** The name of the sound */ @@ -8,17 +15,15 @@ class PlaySoundRawNode extends LogicNode { public var property1: Bool; /** Retrigger */ public var property2: Bool; - /** Override sample rate */ - public var property3: Bool; - /** Playback sample rate */ - public var property4: Int; /** Whether to stream the sound from disk **/ - public var property5: Bool; + public var property3: Bool; - public var property6: String; + public var property4: String; #if lnx_audio - var sound: kha.Sound = null; - var channel: kha.audio1.AudioChannel = null; + var soundName: String = null; + var sound: Sound = null; + var channel: AudioChannel = null; + var sampleRate: Int; #end public function new(tree: LogicTree) { super(tree); @@ -28,24 +33,25 @@ class PlaySoundRawNode extends LogicNode { #if lnx_audio switch (from) { case Play: - if (property6 == 'Sound' ? sound == null : true) { - iron.data.Data.getSound(property6 == 'Sound' ? property0 : inputs[5].get(), function(s: kha.Sound) { - this.sound = s; + var file: String = property4 == 'Sound' ? property0 : inputs[6].get(); + if (soundName != file) { + soundName = file; + Data.getSound(file, function(s: Sound) { + this.sound = cloneSound(s); + this.sampleRate = s.sampleRate; }); } // Resume if (channel != null) { if (property2) channel.stop(); - if (property6 == 'Sound Name') - channel = iron.system.Audio.play(sound, property1, property5); channel.play(); channel.volume = inputs[4].get(); } // Start else if (sound != null) { - if (property3) sound.sampleRate = property4; - channel = iron.system.Audio.play(sound, property1, property5); + sound.sampleRate = Std.int(sampleRate * inputs[5].get()); + channel = Audio.play(sound, property1, property3); channel.volume = inputs[4].get(); } @@ -61,7 +67,7 @@ class PlaySoundRawNode extends LogicNode { tree.removeUpdate(this.onUpdate); runOutput(2); - case UpdateVolume: + case SetVolume: if (channel != null) channel.volume = inputs[4].get(); } #end @@ -81,11 +87,30 @@ class PlaySoundRawNode extends LogicNode { else runOutput(1); } } + + function cloneSound(sound: Sound): Sound { + if (sound == null) return null; + var s = Type.createEmptyInstance(Sound); + s.compressedData = sound.compressedData; + s.uncompressedData = sound.uncompressedData; + s.sampleRate = sound.sampleRate; + s.length = sound.length; + s.channels = sound.channels; + return s; + } + + override function get(from: Int): Dynamic { + #if lnx_audio + return + channel != null ? from == 3 ? channel.length : channel.position : null; + #end + return null; + } } private enum abstract PlayState(Int) from Int to Int { var Play = 0; var Pause = 1; var Stop = 2; - var UpdateVolume = 3; + var SetVolume = 3; } diff --git a/leenkx/Sources/leenkx/logicnode/PlayTilesheetActionNode.hx b/leenkx/Sources/leenkx/logicnode/PlayTilesheetActionNode.hx index 57cf39db..5d405b09 100644 --- a/leenkx/Sources/leenkx/logicnode/PlayTilesheetActionNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PlayTilesheetActionNode.hx @@ -12,9 +12,10 @@ class PlayTilesheetActionNode extends LogicNode { var object: MeshObject = inputs[1].get(); var action: String = inputs[2].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; - object.tilesheet.play(action, function() { + object.activeTilesheet.play(action, function() { runOutput(1); }); diff --git a/leenkx/Sources/leenkx/logicnode/PoissonDiskNode.hx b/leenkx/Sources/leenkx/logicnode/PoissonDiskNode.hx new file mode 100644 index 00000000..3db57da8 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/PoissonDiskNode.hx @@ -0,0 +1,33 @@ +package leenkx.logicnode; + +import leenkx.math.Generator; +import iron.math.Vec4; + +class PoissonDiskNode extends LogicNode { + + public var property0: String; + + var points: Array = []; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + switch(property0) { + case 'Sample Cube': + points = UniformPoissonSampler.SampleCube(inputs[3].get(), inputs[4].get(), inputs[2].get(), inputs[1].get()); + case 'Sample Sphere': + points = UniformPoissonSampler.SampleSphere(inputs[3].get(), inputs[4].get(), inputs[2].get(), inputs[1].get()); + case 'Sample Rectangle': + points = UniformPoissonSampler.SampleRectangle(inputs[3].get(), inputs[4].get(), inputs[2].get(), inputs[1].get()); + case 'Sample Circle': + points = UniformPoissonSampler.SampleCircle(inputs[3].get(), inputs[4].get(), inputs[2].get(), inputs[1].get()); + } + runOutput(0); + } + + override function get(from: Int): Dynamic { + return from == 1 ? points : points.length; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ProceduralNoiseNode.hx b/leenkx/Sources/leenkx/logicnode/ProceduralNoiseNode.hx new file mode 100644 index 00000000..3667ceec --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ProceduralNoiseNode.hx @@ -0,0 +1,48 @@ +package leenkx.logicnode; + +import leenkx.math.Generator; +import iron.math.Vec4; + +class ProceduralNoiseNode extends LogicNode { + + public var property0: String; + + var value: Float; + var p: Perlin = null; + var ds: DiamondSquare = null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + if (property0 == 'Perlin' || property0 == 'Octave Perlin'){ + if (p == null) + p = new Perlin(inputs[4].get()); + if (property0 == 'Perlin') + value = p.perlin(inputs[1].get(), inputs[2].get(), inputs[3].get()); + else + value = p.octavePerlin(inputs[1].get(), inputs[2].get(), inputs[3].get(), inputs[5].get(), inputs[6].get(), inputs[7].get()); + } else { + if (ds == null){ + var width: Int = inputs[3].get(); + var height: Int = inputs[4].get(); + var featureSize: Int = inputs[5].get(); + + width = Std.int(Math.pow(2, Math.ceil(Math.log(width - 1) / Math.log(2)))) + 1; + height = Std.int(Math.pow(2, Math.ceil(Math.log(height - 1) / Math.log(2)))) + 1; + featureSize = Std.int(Math.pow(2, Math.floor(Math.log(featureSize) / Math.log(2)))); + + ds = new DiamondSquare(width, height, featureSize, inputs[6].get(), function(){ return Math.random() - inputs[7].get();}); + ds.diamondSquare(); + } + value = ds.getValue(inputs[1].get(), inputs[2].get()); + } + + runOutput(0); + } + + override function get(from: Int): Dynamic { + return value; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/RandomColorNode.hx b/leenkx/Sources/leenkx/logicnode/RandomColorNode.hx index 31162500..cee7bf5f 100644 --- a/leenkx/Sources/leenkx/logicnode/RandomColorNode.hx +++ b/leenkx/Sources/leenkx/logicnode/RandomColorNode.hx @@ -4,17 +4,27 @@ import iron.math.Vec4; class RandomColorNode extends LogicNode { + public var property0: Bool; + public var property1: Bool; + public var property2: Bool; + public var property3: Bool; + + public var property4: Float; + public var property5: Float; + public var property6: Float; + public var property7: Float; + public function new(tree: LogicTree) { super(tree); } override function get(from: Int): Dynamic { - - var r = Math.random(); - var g = Math.random(); - var b = Math.random(); - // var a = Math.random(); - var v = new Vec4(r, g, b); - return v; + + var r = property0 ? Math.random() : property4; + var g = property1 ? Math.random() : property5; + var b = property2 ? Math.random() : property6; + var a = property3 ? Math.random() : property7; + + return new Vec4(r, g, b, a); } } diff --git a/leenkx/Sources/leenkx/logicnode/RandomObjectNode.hx b/leenkx/Sources/leenkx/logicnode/RandomObjectNode.hx new file mode 100644 index 00000000..a2ad9db6 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/RandomObjectNode.hx @@ -0,0 +1,117 @@ +package leenkx.logicnode; + +import iron.Scene; +import iron.object.Object; +import iron.object.MeshObject; +import iron.data.MeshData; +import iron.data.MaterialData; +import iron.math.Mat4; +import iron.math.Vec4; +import leenkx.object.MeshDataExtension; + +class RandomObjectNode extends LogicNode { + + var object: Object; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + + var numPoints: Int = inputs[4].get(); + if (numPoints < 4) numPoints = 4; + + var mirrorX: Bool = inputs[5].get(); + var mirrorY: Bool = inputs[6].get(); + var mirrorZ: Bool = inputs[7].get(); + + var md: MeshData = null; + + var m: Mat4 = inputs[2].get(); + var matrix: Mat4 = m != null ? m.clone() : null; + + var scale: Vec4 = matrix.getScale(); + + var radiusX: Float = scale.x; + var radiusY: Float = scale.y; + var radiusZ: Float = scale.z; + + while (md == null){ + var points: Array = []; + + for (i in 0...numPoints) { + var x = (Math.random() - 0.5) * radiusX; + var y = (Math.random() - 0.5) * radiusY; + var z = (Math.random() - 0.5) * radiusZ; + + points.push(new Vec4(x, y, z)); + + if (mirrorX) + points.push(new Vec4(-x, y, z)); + + if (mirrorY) + points.push(new Vec4(x, -y, z)); + + if (mirrorZ) + points.push(new Vec4(x, y, -z)); + + if (mirrorX && mirrorY) + points.push(new Vec4(-x, -y, z)); + + if (mirrorX && mirrorZ) + points.push(new Vec4(-x, y, -z)); + + if (mirrorY && mirrorZ) + points.push(new Vec4(x, -y, -z)); + + if (mirrorX && mirrorY && mirrorZ) + points.push(new Vec4(-x, -y, -z)); + } + + md = MeshDataExtension.makeMeshData(points, inputs[8].get(), inputs[9].get()); + + } + + var material: MaterialData = inputs[3].get(); + var mat = new haxe.ds.Vector(1); + mat[0] = material; + + object = cast new MeshObject(md, mat); + + var name: String = inputs[1].get(); + object.name = name != "" ? name : md.name; + + var s = md.scalePos; + var v = new kha.arrays.Float32Array(16); + v[0] = s; v[5] = s; v[10] = s; v[15] = 1.0; + + var dims = new kha.arrays.Float32Array(3); + dims[0] = md.geom.aabb.x; + dims[1] = md.geom.aabb.y; + dims[2] = md.geom.aabb.z; + + object.raw = { + type: "mesh_object", + name: object.name, + data_ref: object.name, + transform: { values: v }, + dimensions: dims + }; + + object.setParent(Scene.active.root); + object.addTrait(new leenkx.trait.internal.UniformsManager()); + + if (matrix != null){ + object.transform.setMatrix(matrix); + object.transform.scale.setFrom(new Vec4(1, 1, 1, 1)); + object.transform.buildMatrix(); + } + + runOutput(0); + } + + override function get(from: Int): Dynamic { + return object; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ReconstructNavMeshNode.hx b/leenkx/Sources/leenkx/logicnode/ReconstructNavMeshNode.hx new file mode 100644 index 00000000..996240bc --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ReconstructNavMeshNode.hx @@ -0,0 +1,23 @@ +package leenkx.logicnode; + +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavMesh; +#end + +class ReconstructNavMeshNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + #if lnx_navigation + var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(inputs[1].get()); + assert(Error, activeNavMesh != null, "No Navigation Mesh Present"); + activeNavMesh.reconstructNavMesh(); + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/RemoveObjectNode.hx b/leenkx/Sources/leenkx/logicnode/RemoveObjectNode.hx index fa681d36..09aacd4e 100644 --- a/leenkx/Sources/leenkx/logicnode/RemoveObjectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/RemoveObjectNode.hx @@ -14,7 +14,7 @@ class RemoveObjectNode extends LogicNode { var removeChildren: Bool = inputs[2].get(); var keepChildrenTransforms: Bool = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } if (removeChildren == false) { for (c in object.children.copy()) { @@ -27,10 +27,6 @@ class RemoveObjectNode extends LogicNode { } } - var raw = iron.Scene.active.raw; - for (g in raw.groups) - iron.Scene.active.getGroup(g.name).remove(object); - object.remove(); runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx b/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx index 2bcb69d8..f420610c 100644 --- a/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx @@ -14,11 +14,11 @@ class RemoveParticleFromObjectNode extends LogicNode { #if lnx_gpu_particles var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var mo = cast(object, iron.object.MeshObject); - if (mo.particleSystems == null) return; + if (mo.particleSystems == null){ runOutput(0); return; } if (property0 == 'All'){ mo.particleSystems = null; diff --git a/leenkx/Sources/leenkx/logicnode/RemovePhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/RemovePhysicsConstraintNode.hx new file mode 100644 index 00000000..7c1a67c5 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/RemovePhysicsConstraintNode.hx @@ -0,0 +1,28 @@ +package leenkx.logicnode; + +import iron.object.Object; +#if lnx_bullet +import leenkx.trait.physics.PhysicsConstraint; +#end + +class RemovePhysicsConstraintNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var pivotObject: Object = inputs[1].get(); + + if (pivotObject == null){ runOutput(0); return; } + +#if lnx_bullet + var con = pivotObject.getTrait(PhysicsConstraint); + + if (con != null) + con.remove(); + +#end + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/RemovePhysicsHookNode.hx b/leenkx/Sources/leenkx/logicnode/RemovePhysicsHookNode.hx new file mode 100644 index 00000000..390ef5bd --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/RemovePhysicsHookNode.hx @@ -0,0 +1,26 @@ +package leenkx.logicnode; + +import iron.object.Object; +#if lnx_bullet +import leenkx.trait.physics.bullet.PhysicsHook; +#end + +class RemovePhysicsHookNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + + if (obj == null){ runOutput(0); return; } + +#if lnx_bullet + var hook = obj.getTrait(PhysicsHook); + if (hook != null) + hook.remove(); +#end + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/RemovePhysicsNode.hx b/leenkx/Sources/leenkx/logicnode/RemovePhysicsNode.hx index 4c054386..602d914a 100644 --- a/leenkx/Sources/leenkx/logicnode/RemovePhysicsNode.hx +++ b/leenkx/Sources/leenkx/logicnode/RemovePhysicsNode.hx @@ -12,12 +12,13 @@ class RemovePhysicsNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } #if lnx_physics - var rigidBody = object.getTrait(RigidBody); + var rb = object.getTrait(RigidBody); - rigidBody.remove(); + if(rb != null) + rb.remove(); #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/RemoveSoftBodyNode.hx b/leenkx/Sources/leenkx/logicnode/RemoveSoftBodyNode.hx new file mode 100644 index 00000000..e85214a8 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/RemoveSoftBodyNode.hx @@ -0,0 +1,40 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.MeshObject; +import iron.data.MeshData; +#if lnx_physics_soft +import leenkx.trait.physics.bullet.SoftBody; +#end + +class RemoveSoftBodyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + if (obj == null) { runOutput(0); return; } + + #if lnx_physics_soft + var restore: Bool = inputs[2].get(); + var sb = obj.getTrait(SoftBody); + if (sb != null) { + sb.remove(); + if (restore) { + var mo = cast(obj, MeshObject); + if (mo != null && mo.data != null) { + new MeshData(mo.data.raw, function(data) { + mo.setData(data); + mo.transform.scaleWorld = 1.0; + mo.transform.buildMatrix(); + }); + } + } + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ReorderRender2DNode.hx b/leenkx/Sources/leenkx/logicnode/ReorderRender2DNode.hx new file mode 100644 index 00000000..9a91c10f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ReorderRender2DNode.hx @@ -0,0 +1,20 @@ +package leenkx.logicnode; + +import iron.App; + +class ReorderRender2DNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + App.moveRender2D(inputs[1].get(), inputs[2].get()); + runOutput(0); + } + + override function get(from: Int): Dynamic { + return @:privateAccess App.traitRenders2D.indexOf(inputs[1].get()); + } + +} diff --git a/leenkx/Sources/leenkx/logicnode/ResumeTilesheetNode.hx b/leenkx/Sources/leenkx/logicnode/ResumeTilesheetNode.hx index c1839527..5d8517ad 100644 --- a/leenkx/Sources/leenkx/logicnode/ResumeTilesheetNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ResumeTilesheetNode.hx @@ -11,9 +11,10 @@ class ResumeTilesheetNode extends LogicNode { override function run(from: Int) { var object: MeshObject = inputs[1].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; - object.tilesheet.resume(); + object.activeTilesheet.resume(); runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/RetainValueNode.hx b/leenkx/Sources/leenkx/logicnode/RetainValueNode.hx index ede18e48..c942cd87 100644 --- a/leenkx/Sources/leenkx/logicnode/RetainValueNode.hx +++ b/leenkx/Sources/leenkx/logicnode/RetainValueNode.hx @@ -15,16 +15,18 @@ class RetainValueNode extends LogicNode { override function run(from: Int) { var retainValue = inputs[1].get(); - switch(Type.getClassName(Type.getClass(retainValue))){ - case "iron.math.Vec4": - value = (cast retainValue: Vec4).clone(); - case "iron.math.Mat4": - value = (cast retainValue: Mat4).clone(); - case "iron.math.Quat": - var q: Quat = new Quat(); - value = q.setFrom((cast retainValue: Quat)); - default: - value = retainValue; + if (retainValue != null){ + switch(Type.getClassName(Type.getClass(retainValue))){ + case "iron.math.Vec4": + value = (cast retainValue: Vec4).clone(); + case "iron.math.Mat4": + value = (cast retainValue: Mat4).clone(); + case "iron.math.Quat": + var q: Quat = new Quat(); + value = q.setFrom((cast retainValue: Quat)); + default: + value = retainValue; + } } runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/SampleObjectPointsNode.hx b/leenkx/Sources/leenkx/logicnode/SampleObjectPointsNode.hx new file mode 100644 index 00000000..122b6287 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SampleObjectPointsNode.hx @@ -0,0 +1,85 @@ +package leenkx.logicnode; + +import iron.math.Vec4; +import iron.object.Object; +import iron.object.MeshObject; + +class SampleObjectPointsNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var object: Object = inputs[0].get(); + if (object == null) return null; + + var sampleCount: Int = inputs[1].get(); + if (sampleCount == null || sampleCount <= 0) sampleCount = 1; + + if (!Std.isOfType(object, MeshObject)) return null; + var mesh = cast(object, MeshObject).data; + if (mesh == null || mesh.geom == null || mesh.geom.indices == null) return null; + + var indices = mesh.geom.indices[0]; + var positions = mesh.geom.positions.values; + + var numTriangles = Math.floor(indices.length / 3); + if (numTriangles == 0) return null; + + var hx = object.transform.scale.x * (1 / 32767); + var hy = object.transform.scale.y * (1 / 32767); + var hz = object.transform.scale.z * (1 / 32767); + if (mesh.raw.scale_pos != null) { + hx *= mesh.raw.scale_pos; + hy *= mesh.raw.scale_pos; + hz *= mesh.raw.scale_pos; + } + + var result: Array<{ point: Vec4, normal: Vec4 }> = []; + + var a1 = 0.8191725133961645; + var a2 = 0.6710436067037892; + var a3 = 0.5497004779019703; + + var seed1 = Math.random(); + var seed2 = Math.random(); + var seed3 = Math.random(); + + for (i in 0...sampleCount) { + var triFrac = (seed1 + i * a1) % 1.0; + var triIndex = Math.floor(triFrac * numTriangles) * 3; + + var iA = indices[triIndex]; + var iB = indices[triIndex + 1]; + var iC = indices[triIndex + 2]; + + var vA = new Vec4(positions[iA * 4] * hx, positions[iA * 4 + 1] * hy, positions[iA * 4 + 2] * hz); + var vB = new Vec4(positions[iB * 4] * hx, positions[iB * 4 + 1] * hy, positions[iB * 4 + 2] * hz); + var vC = new Vec4(positions[iC * 4] * hx, positions[iC * 4 + 1] * hy, positions[iC * 4 + 2] * hz); + + var r1 = (seed2 + i * a2) % 1.0; + var r2 = (seed3 + i * a3) % 1.0; + var sqrtR1 = Math.sqrt(r1); + var weightA = 1 - sqrtR1; + var weightB = sqrtR1 * (1 - r2); + var weightC = sqrtR1 * r2; + + var pointPos = new Vec4(); + pointPos.x = weightA * vA.x + weightB * vB.x + weightC * vC.x; + pointPos.y = weightA * vA.y + weightB * vB.y + weightC * vC.y; + pointPos.z = weightA * vA.z + weightB * vB.z + weightC * vC.z; + pointPos.applyproj(object.transform.world); + + var edge1 = new Vec4(vB.x - vA.x, vB.y - vA.y, vB.z - vA.z); + var edge2 = new Vec4(vC.x - vA.x, vC.y - vA.y, vC.z - vA.z); + var normalVec = edge1.cross(edge2).normalize(); + normalVec.applyQuat(object.transform.rot); + normalVec.normalize(); + + result.push({ point: pointPos, normal: normalVec }); + } + + return result; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetActivationStateNode.hx b/leenkx/Sources/leenkx/logicnode/SetActivationStateNode.hx index 0b268326..1cb275fe 100644 --- a/leenkx/Sources/leenkx/logicnode/SetActivationStateNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetActivationStateNode.hx @@ -19,7 +19,7 @@ class SetActivationStateNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } #if lnx_physics var rigidBody = object.getTrait(RigidBody); diff --git a/leenkx/Sources/leenkx/logicnode/SetAreaLightSizeNode.hx b/leenkx/Sources/leenkx/logicnode/SetAreaLightSizeNode.hx index 2bc34aa1..e29bef28 100644 --- a/leenkx/Sources/leenkx/logicnode/SetAreaLightSizeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetAreaLightSizeNode.hx @@ -13,10 +13,14 @@ class SetAreaLightSizeNode extends LogicNode { var size: Float = inputs[2].get(); var size_y: Float = inputs[3].get(); - if (light == null) return; - - light.data.raw.size = size; - light.data.raw.size_y = size_y; + if (light == null){ runOutput(0); return; } + + #if lnx_ltc + if (light.data.raw.type == "area"){ + light.data.raw.size = size; + light.data.raw.size_y = size_y; + } + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetBoneFkIkOnlyNode.hx b/leenkx/Sources/leenkx/logicnode/SetBoneFkIkOnlyNode.hx index 94b19343..cb437ba6 100644 --- a/leenkx/Sources/leenkx/logicnode/SetBoneFkIkOnlyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetBoneFkIkOnlyNode.hx @@ -20,7 +20,7 @@ class SetBoneFkIkOnlyNode extends LogicNode { var boneName: String = inputs[2].get(); var fk_ik_only: Bool = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; if (anim == null) anim = object.getBoneAnimation(object.uid); diff --git a/leenkx/Sources/leenkx/logicnode/SetCameraProjectionNode.hx b/leenkx/Sources/leenkx/logicnode/SetCameraProjectionNode.hx new file mode 100644 index 00000000..a0e747c4 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCameraProjectionNode.hx @@ -0,0 +1,22 @@ +package leenkx.logicnode; + +import iron.object.CameraObject; + +class SetCameraProjectionNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var camera: CameraObject = inputs[1].get(); + var w: Int = inputs[2].get(); + var h: Int = inputs[3].get(); + + if (camera == null) return; + + camera.buildProjection(w / h); + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx b/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx index f894e28d..c96ea82a 100644 --- a/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx @@ -1,5 +1,6 @@ package leenkx.logicnode; +import iron.object.Object; import iron.object.MeshObject; import iron.object.CameraObject; @@ -12,8 +13,11 @@ class SetCameraRenderFilterNode extends LogicNode { } override function run(from: Int) { - var mo: MeshObject = cast inputs[1].get(); + var object: Object = inputs[1].get(); + var mo: MeshObject = cast object; var camera: CameraObject = inputs[2].get(); + var children: Bool = inputs[3].get(); + var recursive: Bool = inputs[4].get(); assert(Error, Std.isOfType(camera, CameraObject), "Camera must be a camera object!"); @@ -33,6 +37,31 @@ class SetCameraRenderFilterNode extends LogicNode { } } + if (children) setCRFRecursive(property0, object, camera.name, recursive); + runOutput(0); } + + function setCRFRecursive(property: String, object: Object, camName: String, recursive: Bool) { + var objectChildren: Array = object.children; + for (child in objectChildren) { + var mo: MeshObject = cast child; + if (property == 'Add'){ + if (mo.cameraList == null || mo.cameraList.indexOf(camName) == -1){ + if (mo.cameraList == null) mo.cameraList = []; + mo.cameraList.push(camName); + } + } + else{ + if (mo.cameraList != null){ + mo.cameraList.remove(camName); + if (mo.cameraList.length == 0) + mo.cameraList = null; + } + } + if (recursive) setCRFRecursive(property, child, camName, recursive); + } + } + + } \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/SetCurveDataNode.hx b/leenkx/Sources/leenkx/logicnode/SetCurveDataNode.hx new file mode 100644 index 00000000..3bd1be51 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCurveDataNode.hx @@ -0,0 +1,29 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; + +class SetCurveDataNode extends LogicNode { + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var curve: CurveObject = inputs[1].get(); + + if (property0 == "Equidistant Samples") + curve.equidistantSamples = inputs[2].get(); + else if (property0 == "Strength") + curve.data.strength = inputs[2].get(); + else if (property0 == "Color") + curve.data.color = inputs[2].get(); + else { + var resolution: Int = Std.int(Math.max(inputs[2].get(), 1)); + for (index in 0...curve.splinesLength) + curve.data.splines[index].resolution = resolution; + } + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetCurveMeshNode.hx b/leenkx/Sources/leenkx/logicnode/SetCurveMeshNode.hx new file mode 100644 index 00000000..7f4daa2f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCurveMeshNode.hx @@ -0,0 +1,76 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; +import iron.object.MeshObject; +import iron.data.Data; +import iron.data.MeshData; +import iron.data.MaterialData; +import iron.Scene; + +class SetCurveMeshNode extends LogicNode { + public var property0: String; + + var curve: CurveObject; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + curve = inputs[1].get(); + + var mData: MeshData = null; + + switch(property0){ + case 'Extrude': + mData = curve.generateExtrudeMesh(inputs[2].get(), inputs[3].get(), inputs[4].get(), inputs[5].get(), inputs[6].get()); + case 'Bevel': + mData = curve.generateBevelMesh(inputs[2].get(), inputs[3].get(), inputs[4].get(), inputs[5].get(), inputs[6].get()); + case 'Deform': + var mo: MeshObject = inputs[2].get(); + var repetitions: Int = Std.int(Math.max(inputs[4].get(), 1)); + mData = curve.generateDeformedMesh(mo.data, -1, inputs[3].get(), repetitions, inputs[5].get(), inputs[6].get()); + if (mData != null){ + if (curve.curveMesh != null) curve.curveMesh.remove(); + curve.curveMesh = new MeshObject(mData, mo.materials); + curve.curveMesh.name = curve.data.name + "_mesh"; + curve.curveMesh.raw = cast { + name: curve.curveMesh.name, + type: "mesh_object" + }; + curve.curveMesh.setParent(curve); + curve.curveMesh.addTrait(new leenkx.trait.internal.UniformsManager()); + } + } + + if (property0 != 'Deform' && mData != null) { + if (curve.curveMesh != null) curve.curveMesh.remove(); + if (curve.data.material_refs != null && curve.data.material_refs.length > 0) { + var materials = new haxe.ds.Vector(curve.data.material_refs.length); + var loaded = 0; + for (i in 0...curve.data.material_refs.length) { + Data.getMaterial(Scene.active.raw.name, curve.data.material_refs[i], function(mat: MaterialData) { + materials[i] = mat; + loaded++; + if (loaded == curve.data.material_refs.length) { + curve.curveMesh = new MeshObject(mData, materials); + curve.curveMesh.name = curve.data.name + "_mesh"; + curve.curveMesh.raw = cast { + name: curve.curveMesh.name, + type: "mesh_object" + }; + curve.curveMesh.setParent(curve); + curve.curveMesh.addTrait(new leenkx.trait.internal.UniformsManager()); + } + }); + } + } + } + + runOutput(0); + } + + override function get(from: Int): Dynamic { + return curve != null ? curve.curveMesh : null; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetCurveShapeKeyNode.hx b/leenkx/Sources/leenkx/logicnode/SetCurveShapeKeyNode.hx new file mode 100644 index 00000000..6f00ba51 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCurveShapeKeyNode.hx @@ -0,0 +1,21 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; + +class SetCurveShapeKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var curve: CurveObject = inputs[1].get(); + var shapeKey: String = inputs[2].get(); + var value: Float = inputs[3].get(); + + if (curve == null){ runOutput(0); return; } + curve.setShapeKey(shapeKey, value); + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetCurveSplineNode.hx b/leenkx/Sources/leenkx/logicnode/SetCurveSplineNode.hx new file mode 100644 index 00000000..52fd88f4 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCurveSplineNode.hx @@ -0,0 +1,22 @@ +package leenkx.logicnode; + +import iron.object.CurveObject; + +class SetCurveSplineNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var curve: CurveObject = inputs[1].get(); + var index: Int = inputs[2].get(); + + if (index > curve.splinesLength){ runOutput(0); return; } + + curve.data.splines[index].closed = inputs[3].get(); + curve.data.splines[index].resolution = Std.int(Math.max(inputs[4].get(), 1)); + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetDimensionNode.hx b/leenkx/Sources/leenkx/logicnode/SetDimensionNode.hx new file mode 100644 index 00000000..f26c75f1 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetDimensionNode.hx @@ -0,0 +1,23 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; + +class SetDimensionNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var vec:Vec4 = inputs[2].get(); + + object.raw.dimensions[0] = vec.x; + object.raw.dimensions[1] = vec.y; + object.raw.dimensions[2] = vec.z; + + runOutput(0); + + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetFrictionNode.hx b/leenkx/Sources/leenkx/logicnode/SetFrictionNode.hx deleted file mode 100644 index c3eef3ed..00000000 --- a/leenkx/Sources/leenkx/logicnode/SetFrictionNode.hx +++ /dev/null @@ -1,27 +0,0 @@ -package leenkx.logicnode; - -import iron.object.Object; -import leenkx.trait.physics.RigidBody; - -class SetFrictionNode extends LogicNode { - - public function new(tree: LogicTree) { - super(tree); - } - - override function run(from: Int) { - var object: Object = inputs[1].get(); - - if (object == null) return; - - var friction = inputs[2].get(); - -#if lnx_physics - var rigidBody = object.getTrait(RigidBody); - - rigidBody.setFriction(friction); -#end - - runOutput(0); - } -} diff --git a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasFontSizeNode.hx b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasFontSizeNode.hx index f4db8653..ef5a1d6d 100644 --- a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasFontSizeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasFontSizeNode.hx @@ -18,8 +18,11 @@ class SetGlobalCanvasFontSizeNode extends LogicNode { canvas = Scene.active.getTrait(CanvasScript); if (canvas == null) canvas = Scene.active.camera.getTrait(CanvasScript); - canvas.setCanvasFontSize(factor); - runOutput(0); + canvas.notifyOnReady(() -> { + canvas.setCanvasFontSize(factor); + runOutput(0); + }); + } #end } diff --git a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasScaleNode.hx b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasScaleNode.hx index a4832998..4fad939d 100644 --- a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasScaleNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasScaleNode.hx @@ -18,8 +18,11 @@ class SetGlobalCanvasScaleNode extends LogicNode { canvas = Scene.active.getTrait(CanvasScript); if (canvas == null) canvas = Scene.active.camera.getTrait(CanvasScript); - canvas.setUiScale(factor); - runOutput(0); + canvas.notifyOnReady(() -> { + canvas.setUiScale(factor); + runOutput(0); + }); + } #end } diff --git a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasVisibilityNode.hx b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasVisibilityNode.hx index 5238dc59..00d61cb8 100644 --- a/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasVisibilityNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetGlobalCanvasVisibilityNode.hx @@ -4,6 +4,8 @@ import leenkx.trait.internal.CanvasScript; class SetGlobalCanvasVisibilityNode extends LogicNode { + var canvas: CanvasScript; + public function new(tree:LogicTree) { super(tree); } @@ -11,9 +13,13 @@ class SetGlobalCanvasVisibilityNode extends LogicNode { override function run(from: Int) { #if lnx_ui var value: Bool = inputs[1].get(); - CanvasScript.getActiveCanvas().setCanvasVisible(value); - #end + canvas = CanvasScript.getActiveCanvas(); - runOutput(0); + canvas.notifyOnReady(() -> { + canvas.setCanvasVisible(value); + runOutput(0); + }); + + #end } } diff --git a/leenkx/Sources/leenkx/logicnode/SetGravityEnabledNode.hx b/leenkx/Sources/leenkx/logicnode/SetGravityEnabledNode.hx index c2cc7f28..1f9ea5b6 100644 --- a/leenkx/Sources/leenkx/logicnode/SetGravityEnabledNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetGravityEnabledNode.hx @@ -5,9 +5,6 @@ import iron.object.Object; import leenkx.trait.physics.RigidBody; #end -/** - Enable or disable the gravity for a specific object. -**/ class SetGravityEnabledNode extends LogicNode { public function new(tree: LogicTree) { @@ -17,7 +14,7 @@ class SetGravityEnabledNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); var gravityEnabled: Bool = inputs[2].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } #if lnx_physics var body = object.getTrait(RigidBody); diff --git a/leenkx/Sources/leenkx/logicnode/SetHaxePropertyNode.hx b/leenkx/Sources/leenkx/logicnode/SetHaxePropertyNode.hx index 42f21822..73d9914a 100644 --- a/leenkx/Sources/leenkx/logicnode/SetHaxePropertyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetHaxePropertyNode.hx @@ -11,7 +11,7 @@ class SetHaxePropertyNode extends LogicNode { var property: String = inputs[2].get(); var value: Dynamic = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } Reflect.setProperty(object, property, value); diff --git a/leenkx/Sources/leenkx/logicnode/SetLightColorNode.hx b/leenkx/Sources/leenkx/logicnode/SetLightColorNode.hx index eae1f397..565fb98a 100644 --- a/leenkx/Sources/leenkx/logicnode/SetLightColorNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetLightColorNode.hx @@ -12,11 +12,21 @@ class SetLightColorNode extends LogicNode { var light: LightObject = inputs[1].get(); var color: iron.math.Vec4 = inputs[2].get(); - if (light == null) return; + if (light == null){ runOutput(0); return; } + #if lnx_single_point light.data.raw.color[0] = color.x; light.data.raw.color[1] = color.y; light.data.raw.color[2] = color.z; + #else + if (light.data.raw.type == "sun"){ + light.data.raw.color[0] = color.x; + light.data.raw.color[1] = color.y; + light.data.raw.color[2] = color.z; + } + else + light.color = color; + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetLightStrengthNode.hx b/leenkx/Sources/leenkx/logicnode/SetLightStrengthNode.hx index f9fa14d8..3047b2d6 100644 --- a/leenkx/Sources/leenkx/logicnode/SetLightStrengthNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetLightStrengthNode.hx @@ -12,9 +12,16 @@ class SetLightStrengthNode extends LogicNode { var light: LightObject = inputs[1].get(); var strength: Float = inputs[2].get(); - if (light == null) return; + if (light == null){ runOutput(0); return; } - light.data.raw.strength = light.data.raw.type == "sun" ? strength * 0.325 : strength * 0.026; + #if lnx_single_point + light.data.raw.strength = light.data.raw.type == "sun" ? strength * 0.325 : strength * 0.01; + #else + if (light.data.raw.type == "sun") + light.data.raw.strength = strength * 0.325; + else + light.strength = strength * 0.01; + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialImageParamNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialImageParamNode.hx index a73a03b4..0547e59c 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialImageParamNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialImageParamNode.hx @@ -20,19 +20,19 @@ class SetMaterialImageParamNode extends LogicNode { var img: String; object = inputs[1].get(); - if(object == null) return; + if(object == null){ runOutput(0); return; } perObject = inputs[2].get(); if(perObject == null) perObject = false; mat = inputs[3].get(); - if(mat == null) return; + if(mat == null){ runOutput(0); return; } link = inputs[4].get(); - if(link == null) return; + if(link == null){ runOutput(0); return; } img = inputs[5].get(); - if(img == null) return; + if(img == null){ runOutput(0); return; } if(! perObject){ UniformsManager.removeTextureValue(object, mat, link); diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialNode.hx index 75f677fc..ea30d4b1 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialNode.hx @@ -13,7 +13,7 @@ class SetMaterialNode extends LogicNode { var object: MeshObject = inputs[1].get(); var mat: MaterialData = inputs[2].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } for (i in 0...object.materials.length) { object.materials[i] = mat; diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialRgbParamNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialRgbParamNode.hx index 5ddc697b..ee55f788 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialRgbParamNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialRgbParamNode.hx @@ -20,19 +20,19 @@ class SetMaterialRgbParamNode extends LogicNode { var vec: Vec4; object = inputs[1].get(); - if(object == null) return; + if(object == null){ runOutput(0); return; } perObject = inputs[2].get(); if(perObject == null) perObject = false; mat = inputs[3].get(); - if(mat == null) return; + if(mat == null){ runOutput(0); return; } link = inputs[4].get(); - if(link == null) return; + if(link == null){ runOutput(0); return; } vec = inputs[5].get(); - if(vec == null) return; + if(vec == null){ runOutput(0); return; } if(! perObject){ UniformsManager.removeVectorValue(object, mat, link); diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialSlotNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialSlotNode.hx index 819cd8a9..c983974d 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialSlotNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialSlotNode.hx @@ -14,8 +14,8 @@ class SetMaterialSlotNode extends LogicNode { var mat: MaterialData = inputs[2].get(); var slot: Int = inputs[3].get(); - if (object == null) return; - if (slot >= object.materials.length) return; + if (object == null){ runOutput(0); return; } + if (slot >= object.materials.length){ runOutput(0); return; } object.materials[slot] = mat; diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialTextureFilterNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialTextureFilterNode.hx index 45ac61d7..8e7bfdd5 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialTextureFilterNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialTextureFilterNode.hx @@ -16,8 +16,8 @@ class SetMaterialTextureFilterNode extends LogicNode { var name: String = inputs[4].get(); var filter: Int = inputs[5].get(); - if (object == null) return; - if (slot >= object.materials.length) return; + if (object == null){ runOutput(0); return; } + if (slot >= object.materials.length){ runOutput(0); return; } var mo = cast(object, iron.object.MeshObject); diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialValueParamNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialValueParamNode.hx index 47177700..5dfe22b9 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMaterialValueParamNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialValueParamNode.hx @@ -19,19 +19,19 @@ class SetMaterialValueParamNode extends LogicNode { var value: Null; object = inputs[1].get(); - if(object == null) return; + if (object == null){ runOutput(0); return; } perObject = inputs[2].get(); if(perObject == null) perObject = false; mat = inputs[3].get(); - if(mat == null) return; + if(mat == null){ runOutput(0); return; } link = inputs[4].get(); - if(link == null) return; + if(link == null){ runOutput(0); return; } value = inputs[5].get(); - if(value == null) return; + if(value == null){ runOutput(0); return; } if(! perObject){ UniformsManager.removeFloatValue(object, mat, link); diff --git a/leenkx/Sources/leenkx/logicnode/SetMaterialsNode.hx b/leenkx/Sources/leenkx/logicnode/SetMaterialsNode.hx new file mode 100644 index 00000000..ab3ceedd --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetMaterialsNode.hx @@ -0,0 +1,23 @@ +package leenkx.logicnode; + +import iron.object.MeshObject; +import iron.data.MaterialData; + +class SetMaterialsNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: MeshObject = inputs[1].get(); + var mats = inputs[2].get(); + + if (object == null){ runOutput(0); return; } + if (mats == null){ runOutput(0); return; } + + object.materials = mats; + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetMeshNode.hx b/leenkx/Sources/leenkx/logicnode/SetMeshNode.hx index 33a159f5..bdef177f 100644 --- a/leenkx/Sources/leenkx/logicnode/SetMeshNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetMeshNode.hx @@ -13,7 +13,7 @@ class SetMeshNode extends LogicNode { var object: MeshObject = inputs[1].get(); var mesh: MeshData = inputs[2].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } object.data = mesh; diff --git a/leenkx/Sources/leenkx/logicnode/SetNameNode.hx b/leenkx/Sources/leenkx/logicnode/SetNameNode.hx index 08571674..19082098 100644 --- a/leenkx/Sources/leenkx/logicnode/SetNameNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetNameNode.hx @@ -12,7 +12,7 @@ class SetNameNode extends LogicNode { var object: Object = inputs[1].get(); var name: String = inputs[2].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } #if lnx_skin for(a in iron.Scene.active.animations) diff --git a/leenkx/Sources/leenkx/logicnode/SetObjectInstancedNode.hx b/leenkx/Sources/leenkx/logicnode/SetObjectInstancedNode.hx new file mode 100644 index 00000000..ed64d997 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetObjectInstancedNode.hx @@ -0,0 +1,62 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.object.MeshObject; +import kha.arrays.Float32Array; +import kha.graphics4.Usage; + +class SetObjectInstancedNode extends LogicNode { + + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var meshObject: MeshObject = cast inputs[1].get(); + var array = inputs[2].get(); + var include: Bool = inputs[3].get(); + var usage: Bool = inputs[4].get(); + + if (meshObject == null){ runOutput(0); return; } + + var geom = meshObject.data.geom; + + var base = []; + if (property0 == '1') base = [0.0, 0.0, 0.0]; + else if (property0 == '2') base = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; + else if (property0 == '3') base = [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + else base = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + + while (base.length < geom.instanceStride) + base.push(0.0); + + var nid: Float32Array = null; + + if (array != null) + nid = fromArray(include ? base.concat(array) : array); + else + nid = fromArray(base); + + @:privateAccess geom.data.raw.instanced_data = nid; + + if (Std.int(nid.length / geom.instanceStride) != geom.instanceCount) + geom.setupInstanced(nid, @:privateAccess geom.data.raw.instanced_type, usage ? Usage.DynamicUsage : Usage.StaticUsage); + else + geom.updateInstanced(nid); + + runOutput(0); + } + + public static function fromArray(elements: Array): Float32Array { + var len = elements.length; + var nid = new Float32Array(len); + + for (i in 0...len) { + nid[i] = elements[i]; + } + + return nid; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetObjectShapeKeyNode.hx b/leenkx/Sources/leenkx/logicnode/SetObjectShapeKeyNode.hx index 08eacfab..ce01e91d 100644 --- a/leenkx/Sources/leenkx/logicnode/SetObjectShapeKeyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetObjectShapeKeyNode.hx @@ -14,11 +14,13 @@ class SetObjectShapeKeyNode extends LogicNode { var shapeKey: String = inputs[2].get(); var value: Dynamic = inputs[3].get(); - assert(Error, object != null, "Object should not be null"); + if (object == null){ runOutput(0); return; } var morph = cast(object, MeshObject).morphTarget; assert(Error, morph != null, "Object does not have shape keys"); - morph.setMorphValue(shapeKey, value); + + if (morph.hasMorph(shapeKey)) + morph.setMorphValue(shapeKey, value); #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx index cbcfe6e5..2b9829ed 100644 --- a/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx @@ -15,7 +15,7 @@ class SetParticleDataNode extends LogicNode { var object: Object = inputs[1].get(); var slot: Int = inputs[2].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var mo = cast(object, iron.object.MeshObject); diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx index c90729b5..89c0d8d5 100644 --- a/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx @@ -12,7 +12,7 @@ class SetParticleRenderEmitterNode extends LogicNode { #if lnx_gpu_particles var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } cast(object, iron.object.MeshObject).render_emitter = inputs[2].get(); diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx index 39fc64a1..6a06937b 100644 --- a/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx @@ -14,13 +14,13 @@ class SetParticleSpeedNode extends LogicNode { var slot: Int = inputs[2].get(); var speed: Float = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var mo = cast(object, iron.object.MeshObject); var psys = mo.particleSystems != null ? mo.particleSystems[slot] : mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null; - if (psys == null) return; + if (psys == null){ runOutput(0); return; } psys.speed = speed; diff --git a/leenkx/Sources/leenkx/logicnode/SetPitchSoundNode.hx b/leenkx/Sources/leenkx/logicnode/SetPitchSoundNode.hx new file mode 100644 index 00000000..9a40e1ad --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetPitchSoundNode.hx @@ -0,0 +1,22 @@ +package leenkx.logicnode; + +#if lnx_audio +import iron.object.SpeakerObject; +#end + +class SetPitchSoundNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + #if lnx_audio + var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); + if (object == null){ runOutput(0); return; } + object.data.pitch = inputs[2].get(); + object.sound.sampleRate = Std.int(object.sampleRate * object.data.pitch); + #end + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx b/leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx index 3114e6e2..aca5921a 100644 --- a/leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx @@ -15,24 +15,9 @@ class SetPositionSpeakerNode extends LogicNode { override function run(from: Int) { #if lnx_audio var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); - if (object == null || object.sound == null) return; - - var positionInSeconds:Float = inputs[2].get(); - if (positionInSeconds < 0) positionInSeconds = 0; + if (object == null || object.sound == null){ runOutput(0); return; } - var volume = object.data.volume; - var loop = object.data.loop; - var stream = object.data.stream; - - object.stop(); - - var channel = Audio.play(object.sound, loop, stream); - if (channel != null) { - object.channels.push(channel); - channel.volume = volume; - @:privateAccess channel.set_position(positionInSeconds); - } - + object.setPosition(inputs[2].get()); #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetPropertyNode.hx b/leenkx/Sources/leenkx/logicnode/SetPropertyNode.hx index f6152034..3285fadd 100644 --- a/leenkx/Sources/leenkx/logicnode/SetPropertyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetPropertyNode.hx @@ -13,7 +13,7 @@ class SetPropertyNode extends LogicNode { var property: String = inputs[2].get(); var value: Dynamic = inputs[3].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } if (object.properties == null) object.properties = new Map(); object.properties.set(property, value); diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyAnimatedNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyAnimatedNode.hx new file mode 100644 index 00000000..7c863439 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyAnimatedNode.hx @@ -0,0 +1,35 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyAnimatedNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var isAnimated: Bool = inputs[2].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null && rb.body != null) { + var bodyColl: bullet.Bt.CollisionObject = rb.body; + if (isAnimated) { + bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | 2); // CF_KINEMATIC_OBJECT + rb.animated = true; + } else { + bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() & ~2); + rb.animated = false; + } + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyBouncinessNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyBouncinessNode.hx new file mode 100644 index 00000000..c5799887 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyBouncinessNode.hx @@ -0,0 +1,29 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyBouncinessNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var bounciness: Float = inputs[2].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null && rb.body != null) { + rb.restitution = bounciness; + rb.body.setRestitution(bounciness); + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyDampingNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyDampingNode.hx new file mode 100644 index 00000000..c64bbde0 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyDampingNode.hx @@ -0,0 +1,31 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyDampingNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var linear: Float = inputs[2].get(); + var angular: Float = inputs[3].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null && rb.body != null) { + rb.linearDamping = linear; + rb.angularDamping = angular; + + rb.body.setDamping(linear, angular); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyDynamicsNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyDynamicsNode.hx new file mode 100644 index 00000000..85da719e --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyDynamicsNode.hx @@ -0,0 +1,43 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyDynamicsNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var isStatic: Bool = inputs[2].get(); + var mass: Float = inputs[3].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null && rb.body != null) { + + var finalMass = isStatic ? 0.0 : mass; + + rb.mass = finalMass; + rb.staticObj = isStatic; + + var inertia = new bullet.Bt.Vector3(0, 0, 0); + + if (!isStatic && finalMass > 0) { + rb.btshape.calculateLocalInertia(finalMass, inertia); + } + + rb.body.setMassProps(finalMass, inertia); + rb.body.updateInertiaTensor(); + + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyFactorNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyFactorNode.hx new file mode 100644 index 00000000..4f57c54f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyFactorNode.hx @@ -0,0 +1,37 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; +import leenkx.trait.physics.RigidBody; +#if lnx_bullet +import bullet.Bt.Vector3; +#end + +class SetRigidBodyFactorNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + var linFac: Vec4 = inputs[2].get(); + var angFac: Vec4 = inputs[3].get(); + + if (obj == null) return; + + #if lnx_physics + var rb = obj.getTrait(RigidBody); + if (rb != null) { + var lf = new Vector3(linFac.x, linFac.y, linFac.z); + var af = new Vector3(angFac.x, angFac.y, angFac.z); + + rb.body.setLinearFactor(lf); + rb.body.setAngularFactor(af); + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyFrictionNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyFrictionNode.hx new file mode 100644 index 00000000..beee2381 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyFrictionNode.hx @@ -0,0 +1,34 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyFrictionNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var obj: Object = inputs[1].get(); + var friction: Float = inputs[2].get(); + var angFriction: Float = inputs[3].get(); + + if (obj == null){ runOutput(0); return; } + + #if lnx_physics + var rb = obj.getTrait(RigidBody); + if (rb != null) { + rb.friction = friction; + rb.angularFriction = angFriction; + + rb.setFriction(friction); + rb.setAngularFriction(angFriction); + + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyGravityNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyGravityNode.hx new file mode 100644 index 00000000..90cd1ba5 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyGravityNode.hx @@ -0,0 +1,29 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; +#if lnx_physics +import leenkx.trait.physics.RigidBody; +#end + +class SetRigidBodyGravityNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var gravity: Vec4 = inputs[2].get(); + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var body = object.getTrait(RigidBody); + if (body != null) { + body.setGravity(gravity); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyTriggerNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyTriggerNode.hx new file mode 100644 index 00000000..51ec6c8f --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyTriggerNode.hx @@ -0,0 +1,33 @@ +package leenkx.logicnode; + +import iron.object.Object; +import leenkx.trait.physics.RigidBody; + +class SetRigidBodyTriggerNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + var isTrigger: Bool = inputs[2].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_physics + var rb = object.getTrait(RigidBody); + if (rb != null && rb.body != null) { + var bodyColl: bullet.Bt.CollisionObject = rb.body; + if (isTrigger) { + bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | 4); // CF_NO_CONTACT_RESPONSE + } else { + bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() & ~4); + } + rb.activate(); + } + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRigidBodyVelocityNode.hx b/leenkx/Sources/leenkx/logicnode/SetRigidBodyVelocityNode.hx new file mode 100644 index 00000000..21692320 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetRigidBodyVelocityNode.hx @@ -0,0 +1,59 @@ +package leenkx.logicnode; + +import iron.object.Object; +import iron.math.Vec4; +import leenkx.trait.physics.RigidBody; + +using leenkx.object.TransformExtension; + +class SetRigidBodyVelocityNode extends LogicNode { + + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + if (object == null) { + runOutput(0); + return; + } + +#if lnx_physics + var rb: RigidBody = object.getTrait(RigidBody); + if (rb != null) { + rb.activate(); + + switch (property0) { + case "Both": + var linear: Vec4 = inputs[2].get(); + var localLinear: Bool = inputs[3].get(); + var angular: Vec4 = inputs[4].get(); + var localAngular: Bool = inputs[5].get(); + if (localLinear) + linear = object.transform.worldVecToOrientation(linear); + rb.setLinearVelocity(linear.x, linear.y, linear.z); + if (localAngular) + angular = object.transform.worldVecToOrientation(angular); + rb.setAngularVelocity(angular.x, angular.y, angular.z); + case "Linear": + var linear: Vec4 = inputs[2].get(); + var localLinear: Bool = inputs[3].get(); + if (localLinear) + linear = object.transform.worldVecToOrientation(linear); + rb.setLinearVelocity(linear.x, linear.y, linear.z); + case "Angular": + var angular: Vec4 = inputs[4].get(); + var localAngular: Bool = inputs[5].get(); + if (localAngular) + angular = object.transform.worldVecToOrientation(angular); + rb.setAngularVelocity(angular.x, angular.y, angular.z); + } + } +#end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetRotationNode.hx b/leenkx/Sources/leenkx/logicnode/SetRotationNode.hx index 912677a5..28a3566e 100644 --- a/leenkx/Sources/leenkx/logicnode/SetRotationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetRotationNode.hx @@ -2,12 +2,14 @@ package leenkx.logicnode; import iron.object.Object; +import iron.math.Mat4; +import iron.math.Vec4; import iron.math.Quat; import leenkx.trait.physics.RigidBody; class SetRotationNode extends LogicNode { - public var property0: String; // UNUSED + public var property0 = "Local"; public function new(tree: LogicTree) { super(tree); @@ -15,12 +17,30 @@ class SetRotationNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } var _q: Quat = inputs[2].get(); - if (_q == null) return; + if (_q == null){ runOutput(0); return; } final q = new Quat(_q.x, _q.y, _q.z, _q.w).normalize(); - object.transform.rot.setFrom(q); + + switch (property0){ + case "Local": + object.transform.rot.setFrom(q); + case "Global": + var newLocalRot = new Quat(); + if (object.parent != null) { + var parentGlobalRot = new Quat(); + var tempPos = new Vec4(); + var tempScale = new Vec4(); + object.parent.transform.world.decompose(tempPos, parentGlobalRot, tempScale); + var invParentRot = new Quat().inverse(parentGlobalRot); + newLocalRot.multquats(invParentRot, q); + } else { + newLocalRot.setFrom(q); + } + object.transform.rot.setFrom(newLocalRot); + } + object.transform.buildMatrix(); #if lnx_physics diff --git a/leenkx/Sources/leenkx/logicnode/SetScreenCamerasNode.hx b/leenkx/Sources/leenkx/logicnode/SetScreenCamerasNode.hx new file mode 100644 index 00000000..59621047 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetScreenCamerasNode.hx @@ -0,0 +1,66 @@ +package leenkx.logicnode; + +import iron.Scene; +import iron.object.CameraObject; +import leenkx.renderpath.RenderPathCreator; + +class SetScreenCamerasNode extends LogicNode { + var originalDrawMeshes: Void -> Void; + var originalCamera: CameraObject; + var cameras: Array; + var viewports: Array<{x:Int, y:Int, w:Int, h:Int}>; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + if (from == 0) { + var numCams = Std.int((inputs.length - 2) / 5); + cameras = []; + viewports = []; + + for (i in 0...numCams) { + var vx = inputs[2 + i * 5 + 1].get(); + var vy = inputs[2 + i * 5 + 2].get(); + var vw = inputs[2 + i * 5 + 3].get(); + var vh = inputs[2 + i * 5 + 4].get(); + viewports.push({x: vx, y: vy, w: vw, h: vh}); + + var cam: CameraObject = inputs[2 + i * 5].get(); + cam.buildProjection(vw / vh); + cam.buildMatrix(); + cameras.push(cam); + } + + if (originalDrawMeshes == null) { + originalCamera = Scene.active.camera; + originalDrawMeshes = RenderPathCreator.drawMeshes; + + RenderPathCreator.drawMeshes = function() { + var g = RenderPathCreator.path.currentG; + for (i in 0...cameras.length) { + RenderPathCreator.setTargetMeshes(); + Scene.active.camera = cameras[i]; + Scene.active.camera.buildMatrix(); + g.viewport(viewports[i].x, viewports[i].y, viewports[i].w, viewports[i].h); + originalDrawMeshes(); + } + }; + } + + runOutput(0); + + } else { + if (originalDrawMeshes != null) { + RenderPathCreator.drawMeshes = originalDrawMeshes; + originalDrawMeshes = null; + Scene.active.camera = originalCamera; + originalCamera = null; + Scene.active.camera.buildProjection(); + } + + runOutput(1); + } + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetShaderUniformNode.hx b/leenkx/Sources/leenkx/logicnode/SetShaderUniformNode.hx index 2b8753f6..bfa9d509 100644 --- a/leenkx/Sources/leenkx/logicnode/SetShaderUniformNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetShaderUniformNode.hx @@ -11,6 +11,7 @@ class SetShaderUniformNode extends LogicNode { static var vec2Map = new Map(); static var vec3Map = new Map(); static var vec4Map = new Map(); + static var texMap = new Map(); /** Uniform type **/ public var property0: String; @@ -24,6 +25,7 @@ class SetShaderUniformNode extends LogicNode { iron.object.Uniforms.externalVec2Links.push(vec2Link); iron.object.Uniforms.externalVec3Links.push(vec3Link); iron.object.Uniforms.externalVec4Links.push(vec4Link); + iron.object.Uniforms.externalTextureLinks.push(textureLink); } } @@ -36,7 +38,28 @@ class SetShaderUniformNode extends LogicNode { case "float": floatMap.set(uniformName, inputs[2].get()); case "vec2": vec2Map.set(uniformName, inputs[2].get()); case "vec3": vec3Map.set(uniformName, inputs[2].get()); - case "vec4": vec4Map.set(uniformName, inputs[2].get()); + case "vec4": + //vec4Map.set(uniformName, inputs[2].get()); + vec3Map.set(uniformName+'_xyz', inputs[2].get()); + floatMap.set(uniformName+'_w', inputs[2].get().w); + case "sampler2D": + var assetName: String = inputs[2].get(); + if (assetName == null || assetName == "") return; + + var img = iron.data.Data.cachedImages.get(assetName); + + if (img != null) { + texMap.set(uniformName, img); + runOutput(0); + } + else { + iron.data.Data.getImage(assetName, function(loadedImg: kha.Image) { + if (loadedImg != null) { + texMap.set(uniformName, loadedImg); + runOutput(0); + } + }); + } default: } @@ -62,4 +85,8 @@ class SetShaderUniformNode extends LogicNode { static function vec4Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { return vec4Map.get(link); } + + static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + return texMap.get(link); + } } diff --git a/leenkx/Sources/leenkx/logicnode/SetSpotLightBlendNode.hx b/leenkx/Sources/leenkx/logicnode/SetSpotLightBlendNode.hx index 7600c9be..2a0104a2 100644 --- a/leenkx/Sources/leenkx/logicnode/SetSpotLightBlendNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetSpotLightBlendNode.hx @@ -12,9 +12,17 @@ class SetSpotLightBlendNode extends LogicNode { var light: LightObject = inputs[1].get(); var blend: Float = inputs[2].get(); - if (light == null) return; + if (light == null){ runOutput(0); return; } - light.data.raw.spot_blend = blend; + #if lnx_spot + if (light.data.raw.type == "spot"){ + #if lnx_single_point + light.data.raw.spot_blend = blend / 10; + #else + light.blend = blend / 10; + #end + } + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetSpotLightSizeNode.hx b/leenkx/Sources/leenkx/logicnode/SetSpotLightSizeNode.hx index 1058219a..cfcbd148 100644 --- a/leenkx/Sources/leenkx/logicnode/SetSpotLightSizeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetSpotLightSizeNode.hx @@ -12,9 +12,17 @@ class SetSpotLightSizeNode extends LogicNode { var light: LightObject = inputs[1].get(); var size: Float = inputs[2].get(); - if (light == null) return; - - light.data.raw.spot_size = size; + if (light == null){ runOutput(0); return; } + + #if lnx_spot + if (light.data.raw.type == "spot"){ + #if lnx_single_point + light.data.raw.spot_size = Math.cos(size / 2); + #else + light.size = Math.cos(size / 2); + #end + } + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetTilesheetActionNode.hx b/leenkx/Sources/leenkx/logicnode/SetTilesheetActionNode.hx index 5f242405..0f0b62d1 100644 --- a/leenkx/Sources/leenkx/logicnode/SetTilesheetActionNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetTilesheetActionNode.hx @@ -12,7 +12,8 @@ class SetTilesheetActionNode extends LogicNode { var object: MeshObject = inputs[1].get(); var action: String = inputs[2].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; object.setTilesheetAction(action); diff --git a/leenkx/Sources/leenkx/logicnode/SetTilesheetFlipNode.hx b/leenkx/Sources/leenkx/logicnode/SetTilesheetFlipNode.hx index 2cff1408..3be48014 100644 --- a/leenkx/Sources/leenkx/logicnode/SetTilesheetFlipNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetTilesheetFlipNode.hx @@ -14,9 +14,9 @@ class SetTilesheetFlipNode extends LogicNode { var flipY: Bool = inputs[3].get(); if (object == null) return; - if (object.tilesheet == null) return; - object.tilesheet.flipX = flipX; - object.tilesheet.flipY = flipY; + if (object.activeTilesheet == null) return; + object.activeTilesheet.flipX = flipX; + object.activeTilesheet.flipY = flipY; runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetTilesheetFrameNode.hx b/leenkx/Sources/leenkx/logicnode/SetTilesheetFrameNode.hx index 421c840a..e0f478f0 100644 --- a/leenkx/Sources/leenkx/logicnode/SetTilesheetFrameNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetTilesheetFrameNode.hx @@ -12,9 +12,10 @@ class SetTilesheetFrameNode extends LogicNode { var object: MeshObject = inputs[1].get(); var frame: Int = inputs[2].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; - object.tilesheet.setFrameOffset(frame); + object.activeTilesheet.setFrameOffset(frame); runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetTilesheetPausedNode.hx b/leenkx/Sources/leenkx/logicnode/SetTilesheetPausedNode.hx index 5c29d435..cf21e21d 100644 --- a/leenkx/Sources/leenkx/logicnode/SetTilesheetPausedNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetTilesheetPausedNode.hx @@ -12,9 +12,10 @@ class SetTilesheetPausedNode extends LogicNode { var object: MeshObject = inputs[1].get(); var paused: Bool = inputs[2].get(); - if (object == null || object.tilesheet == null) return; + if (object == null){ runOutput(0); return; } + if (object.activeTilesheet == null) return; - paused ? object.tilesheet.pause() : object.tilesheet.resume(); + paused ? object.activeTilesheet.pause() : object.activeTilesheet.resume(); runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/SetVelocityNode.hx b/leenkx/Sources/leenkx/logicnode/SetVelocityNode.hx deleted file mode 100644 index 6276e380..00000000 --- a/leenkx/Sources/leenkx/logicnode/SetVelocityNode.hx +++ /dev/null @@ -1,33 +0,0 @@ -package leenkx.logicnode; - -import iron.object.Object; -import iron.math.Vec4; -import leenkx.trait.physics.RigidBody; - -class SetVelocityNode extends LogicNode { - - public function new(tree: LogicTree) { - super(tree); - } - - override function run(from: Int) { - var object: Object = inputs[1].get(); - var linear: Vec4 = inputs[2].get(); - var linearFactor: Vec4 = inputs[3].get(); - var angular: Vec4 = inputs[4].get(); - var angularFactor: Vec4 = inputs[5].get(); - - if (object == null || linear == null || linearFactor == null || angular == null || angularFactor == null) return; - -#if lnx_physics - var rb: RigidBody = object.getTrait(RigidBody); - rb.activate(); - rb.setLinearVelocity(linear.x, linear.y, linear.z); - rb.setLinearFactor(linearFactor.x, linearFactor.y, linearFactor.z); - rb.setAngularVelocity(angular.x, angular.y, angular.z); - rb.setAngularFactor(angularFactor.x, angularFactor.y, angularFactor.z); -#end - - runOutput(0); - } -} diff --git a/leenkx/Sources/leenkx/logicnode/SetVirtualButtonNode.hx b/leenkx/Sources/leenkx/logicnode/SetVirtualButtonNode.hx new file mode 100644 index 00000000..5a890bfa --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetVirtualButtonNode.hx @@ -0,0 +1,30 @@ +package leenkx.logicnode; + +class SetVirtualButtonNode extends LogicNode { + + public var property0: String; + public var property1: String; + public var property2: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + switch (property0) { + case "Keyboard": + var keyboard = iron.system.Input.getKeyboard(); + keyboard.setVirtual(property1, property2); + case "Mouse": + var mouse = iron.system.Input.getMouse(); + mouse.setVirtual(property1, property2); + case "Gamepad": + var gamepad = iron.system.Input.getGamepad(0); + if (gamepad != null) { + gamepad.setVirtual(property1, property2); + } + } + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetVisibleNode.hx b/leenkx/Sources/leenkx/logicnode/SetVisibleNode.hx index 95d780d2..e33ace31 100644 --- a/leenkx/Sources/leenkx/logicnode/SetVisibleNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetVisibleNode.hx @@ -16,7 +16,7 @@ class SetVisibleNode extends LogicNode { var children: Bool = inputs[3].get(); var recursive: Bool = inputs[4].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } switch (property0) { case "object": diff --git a/leenkx/Sources/leenkx/logicnode/SetVolumeSoundNode.hx b/leenkx/Sources/leenkx/logicnode/SetVolumeSoundNode.hx index 0a30a588..a275aacf 100644 --- a/leenkx/Sources/leenkx/logicnode/SetVolumeSoundNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetVolumeSoundNode.hx @@ -11,7 +11,7 @@ class SetVolumeSoundNode extends LogicNode { override function run(from: Int) { #if lnx_audio var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); - if (object == null) return; + if (object == null){ runOutput(0); return; } object.setVolume(inputs[2].get()); #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/SetWorldNode.hx b/leenkx/Sources/leenkx/logicnode/SetWorldNode.hx index 39e2c484..bb626dc4 100644 --- a/leenkx/Sources/leenkx/logicnode/SetWorldNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetWorldNode.hx @@ -1,6 +1,8 @@ package leenkx.logicnode; import iron.data.SceneFormat; +import iron.data.WorldData; +import leenkx.renderpath.RenderPathCreator; class SetWorldNode extends LogicNode { @@ -12,10 +14,12 @@ class SetWorldNode extends LogicNode { var world: String = inputs[1].get(); if (world != null){ + iron.data.Data.getWorld(iron.Scene.active.raw.name, world, function(world: WorldData) { + iron.Scene.active.world = world; + }); + iron.Scene.active.raw.world_ref = world; - var npath = leenkx.renderpath.RenderPathCreator.get(); - npath.loadShader("shader_datas/World_" + world + "/World_" + world); - iron.RenderPath.setActive(npath); + RenderPathCreator.path.loadShader("shader_datas/World_" + world + "/World_" + world); } runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/SetWorldProbeNode.hx b/leenkx/Sources/leenkx/logicnode/SetWorldProbeNode.hx new file mode 100644 index 00000000..d4078125 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetWorldProbeNode.hx @@ -0,0 +1,28 @@ +package leenkx.logicnode; + +import iron.data.SceneFormat; +import iron.data.WorldData; + +class SetWorldProbeNode extends LogicNode { + + var probe = null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + if (from == 0){ + var world: String = inputs[2].get(); + iron.data.Data.getWorld(iron.Scene.active.raw.name, world, function(world: WorldData) { + probe = iron.Scene.active.world.probe; + iron.Scene.active.world.probe = world.probe; + }); + } + else + if (probe != null) + iron.Scene.active.world.probe = probe; + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetWorldStrengthNode.hx b/leenkx/Sources/leenkx/logicnode/SetWorldStrengthNode.hx index f2f03e91..b41379d7 100644 --- a/leenkx/Sources/leenkx/logicnode/SetWorldStrengthNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetWorldStrengthNode.hx @@ -8,7 +8,10 @@ class SetWorldStrengthNode extends LogicNode { override function run(from: Int) { - iron.Scene.active.world.raw.probe.strength = inputs[1].get(); + if (iron.Scene.active.world.raw.turbidity != null) + iron.Scene.active.world.raw.probe.strength = inputs[1].get() * 0.1; + else + iron.Scene.active.world.raw.probe.strength = inputs[1].get(); runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/SpawnObjectByNameNode.hx b/leenkx/Sources/leenkx/logicnode/SpawnObjectByNameNode.hx index 76ed3698..1c000703 100644 --- a/leenkx/Sources/leenkx/logicnode/SpawnObjectByNameNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SpawnObjectByNameNode.hx @@ -9,7 +9,6 @@ import leenkx.trait.physics.RigidBody; class SpawnObjectByNameNode extends LogicNode { var object: Object; - var matrices: Array = []; /** Scene from which to take the object **/ public var property0: Null; @@ -20,19 +19,20 @@ class SpawnObjectByNameNode extends LogicNode { override function run(from: Int) { var objectName = inputs[1].get(); - if (objectName == null) return; + if (objectName == null){ runOutput(0); return; } + var sceneName = property0; #if lnx_json - property0 += ".json"; + sceneName += ".json"; #elseif lnx_compress - property0 += ".lz4"; + sceneName += ".lz4"; #end var m: Mat4 = inputs[2].get(); - matrices.push(m != null ? m.clone() : null); - var spawnChildren: Bool = inputs.length > 3 ? inputs[3].get() : true; // TODO + var matrix: Mat4 = m != null ? m.clone() : null; + var spawnChildren: Bool = inputs[3].get(); - Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> { + Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> { //Check if object with given name present in the specified scene var objPresent: Bool = false; @@ -43,12 +43,11 @@ class SpawnObjectByNameNode extends LogicNode { break; } } - if (! objPresent) return; + if (! objPresent){ runOutput(0); return; } //Spawn object if present iron.Scene.active.spawnObject(objectName, null, function(o: Object) { object = o; - var matrix = matrices.pop(); // Async spawn in a loop, order is non-stable if (matrix != null) { object.transform.setMatrix(matrix); #if lnx_physics diff --git a/leenkx/Sources/leenkx/logicnode/SpawnObjectNode.hx b/leenkx/Sources/leenkx/logicnode/SpawnObjectNode.hx index 38cf9778..675a287d 100644 --- a/leenkx/Sources/leenkx/logicnode/SpawnObjectNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SpawnObjectNode.hx @@ -7,7 +7,6 @@ import leenkx.trait.physics.RigidBody; class SpawnObjectNode extends LogicNode { var object: Object; - var matrices: Array = []; public function new(tree: LogicTree) { super(tree); @@ -15,18 +14,17 @@ class SpawnObjectNode extends LogicNode { override function run(from: Int) { var objectInput = inputs[1].get(); - if (objectInput == null) return; + if (objectInput == null){ runOutput(0); return; } var objectName = objectInput.name; if (objectName == "") objectName = tree.object.name; var m: Mat4 = inputs[2].get(); - matrices.push(m != null ? m.clone() : null); - var spawnChildren: Bool = inputs.length > 3 ? inputs[3].get() : true; // TODO + var matrix: Mat4 = m != null ? m.clone() : null; + var spawnChildren: Bool = inputs[3].get(); iron.Scene.active.spawnObject(objectName, null, function(o: Object) { object = o; - var matrix = matrices.pop(); // Async spawn in a loop, order is non-stable if (matrix != null) { object.transform.setMatrix(matrix); #if lnx_physics diff --git a/leenkx/Sources/leenkx/logicnode/SplitStringNode.hx b/leenkx/Sources/leenkx/logicnode/SplitStringNode.hx index 74698992..45861a47 100644 --- a/leenkx/Sources/leenkx/logicnode/SplitStringNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SplitStringNode.hx @@ -11,6 +11,6 @@ class SplitStringNode extends LogicNode { var s2: String = inputs[1].get(); if (s1 == null || s2 == null) return null; - return s1.split(s2); + return from == 0 ? s1.split(s2) : s1.split(s2).length; } } diff --git a/leenkx/Sources/leenkx/logicnode/StopAgentNode.hx b/leenkx/Sources/leenkx/logicnode/StopAgentNode.hx index 9ae31805..48e85807 100644 --- a/leenkx/Sources/leenkx/logicnode/StopAgentNode.hx +++ b/leenkx/Sources/leenkx/logicnode/StopAgentNode.hx @@ -2,6 +2,11 @@ package leenkx.logicnode; import iron.object.Object; +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavAgent; +#end + class StopAgentNode extends LogicNode { public function new(tree: LogicTree) { @@ -11,12 +16,13 @@ class StopAgentNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } -#if lnx_navigation - var agent: leenkx.trait.NavAgent = object.getTrait(leenkx.trait.NavAgent); - agent.stop(); -#end + #if lnx_navigation + var agent: NavAgent = object.getTrait(NavAgent); + assert(Error, agent != null, "Object does not have a NavAgent trait"); + agent.stop(); + #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/StopCrowdNode.hx b/leenkx/Sources/leenkx/logicnode/StopCrowdNode.hx new file mode 100644 index 00000000..d6714f71 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/StopCrowdNode.hx @@ -0,0 +1,29 @@ +package leenkx.logicnode; + +import iron.object.Object; + +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +import leenkx.trait.NavCrowd; +#end + +class StopCrowdNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var object: Object = inputs[1].get(); + + if (object == null){ runOutput(0); return; } + + #if lnx_navigation + var crowdAgent: NavCrowd = object.getTrait(NavCrowd); + assert(Error, crowdAgent != null, "Object does not have a NavCrowd trait"); + crowdAgent.crowdAgentTeleport(object.transform.world.getLoc()); + #end + + runOutput(0); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/StopSoundNode.hx b/leenkx/Sources/leenkx/logicnode/StopSoundNode.hx index 87c33986..85a404c4 100644 --- a/leenkx/Sources/leenkx/logicnode/StopSoundNode.hx +++ b/leenkx/Sources/leenkx/logicnode/StopSoundNode.hx @@ -11,7 +11,7 @@ class StopSoundNode extends LogicNode { override function run(from: Int) { #if lnx_audio var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject); - if (object == null) return; + if (object == null){ runOutput(0); return; } object.stop(); #end runOutput(0); diff --git a/leenkx/Sources/leenkx/logicnode/TimeNode.hx b/leenkx/Sources/leenkx/logicnode/TimeNode.hx index 0532482b..bd1202ed 100644 --- a/leenkx/Sources/leenkx/logicnode/TimeNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TimeNode.hx @@ -1,5 +1,7 @@ package leenkx.logicnode; +import iron.system.Time; + class TimeNode extends LogicNode { public function new(tree: LogicTree) { @@ -7,7 +9,10 @@ class TimeNode extends LogicNode { } override function get(from: Int): Dynamic { - if (from == 0) return iron.system.Time.time(); - else return iron.system.Time.delta; + return switch (from) { + case 0: Time.time(); + case 1: Time.delta; + default: Time.realTime(); + } } } diff --git a/leenkx/Sources/leenkx/logicnode/TransformMathNode.hx b/leenkx/Sources/leenkx/logicnode/TransformMathNode.hx index b76ec543..3e9ccdc8 100644 --- a/leenkx/Sources/leenkx/logicnode/TransformMathNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TransformMathNode.hx @@ -15,10 +15,20 @@ class TransformMathNode extends LogicNode { var m1: Mat4 = inputs[0].get(); var m2: Mat4 = inputs[1].get(); - if (m1 == null || m2 == null) return null; + if (m1 == null) return null; m.setFrom(m1); - transformMath(m, m2); + + switch (property0) { + case "Multiply": + if (m2 == null) return null; + m.multmat(m2); + case "Invert": + m.getInverse(m); + case "Transform Math": + if (m2 == null) return null; + transformMath(m, m2); + } return m; } diff --git a/leenkx/Sources/leenkx/logicnode/TranslateOnLocalAxisNode.hx b/leenkx/Sources/leenkx/logicnode/TranslateOnLocalAxisNode.hx index 7af4cc72..2d78d14c 100644 --- a/leenkx/Sources/leenkx/logicnode/TranslateOnLocalAxisNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TranslateOnLocalAxisNode.hx @@ -15,7 +15,7 @@ class TranslateOnLocalAxisNode extends LogicNode { var l: Int = inputs[3].get(); var ini: Bool = inputs[4].get(); - if (object == null) return; + if (object == null){ runOutput(0); return; } if (ini) sp *= -1; diff --git a/leenkx/Sources/leenkx/logicnode/TweenFloatNode.hx b/leenkx/Sources/leenkx/logicnode/TweenFloatNode.hx index a8a26290..9c46acb9 100644 --- a/leenkx/Sources/leenkx/logicnode/TweenFloatNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TweenFloatNode.hx @@ -80,6 +80,18 @@ class TweenFloatNode extends LogicNode { type = BackOut; case "BackInOut": type = BackInOut; + case "BounceIn": + type = BounceIn; + case "BounceOut": + type = BounceOut; + case "BounceInOut": + type = BounceInOut; + case "ElasticIn": + type = ElasticIn; + case "ElasticOut": + type = ElasticOut; + case "ElasticInOut": + type = ElasticInOut; } anim = Tween.to({ diff --git a/leenkx/Sources/leenkx/logicnode/TweenRotationNode.hx b/leenkx/Sources/leenkx/logicnode/TweenRotationNode.hx index 95990e3e..186bf9f5 100644 --- a/leenkx/Sources/leenkx/logicnode/TweenRotationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TweenRotationNode.hx @@ -81,6 +81,18 @@ class TweenRotationNode extends LogicNode { type = BackOut; case "BackInOut": type = BackInOut; + case "BounceIn": + type = BounceIn; + case "BounceOut": + type = BounceOut; + case "BounceInOut": + type = BounceInOut; + case "ElasticIn": + type = ElasticIn; + case "ElasticOut": + type = ElasticOut; + case "ElasticInOut": + type = ElasticInOut; } anim = Tween.to({ diff --git a/leenkx/Sources/leenkx/logicnode/TweenTransformNode.hx b/leenkx/Sources/leenkx/logicnode/TweenTransformNode.hx index 50eaeace..9f1298f5 100644 --- a/leenkx/Sources/leenkx/logicnode/TweenTransformNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TweenTransformNode.hx @@ -93,6 +93,18 @@ class TweenTransformNode extends LogicNode { type = BackOut; case "BackInOut": type = BackInOut; + case "BounceIn": + type = BounceIn; + case "BounceOut": + type = BounceOut; + case "BounceInOut": + type = BounceInOut; + case "ElasticIn": + type = ElasticIn; + case "ElasticOut": + type = ElasticOut; + case "ElasticInOut": + type = ElasticInOut; } anim = Tween.to({ diff --git a/leenkx/Sources/leenkx/logicnode/TweenVectorNode.hx b/leenkx/Sources/leenkx/logicnode/TweenVectorNode.hx index d4a60612..dd36a284 100644 --- a/leenkx/Sources/leenkx/logicnode/TweenVectorNode.hx +++ b/leenkx/Sources/leenkx/logicnode/TweenVectorNode.hx @@ -81,6 +81,18 @@ class TweenVectorNode extends LogicNode { type = BackOut; case "BackInOut": type = BackInOut; + case "BounceIn": + type = BounceIn; + case "BounceOut": + type = BounceOut; + case "BounceInOut": + type = BounceInOut; + case "ElasticIn": + type = ElasticIn; + case "ElasticOut": + type = ElasticOut; + case "ElasticInOut": + type = ElasticInOut; } anim = Tween.to({ diff --git a/leenkx/Sources/leenkx/logicnode/WorldToScreenSpaceNode.hx b/leenkx/Sources/leenkx/logicnode/WorldToScreenSpaceNode.hx index fc8b5816..8d579dca 100644 --- a/leenkx/Sources/leenkx/logicnode/WorldToScreenSpaceNode.hx +++ b/leenkx/Sources/leenkx/logicnode/WorldToScreenSpaceNode.hx @@ -31,6 +31,8 @@ class WorldToScreenSpaceNode extends LogicNode { var w = App.w(); var h = App.h(); - return new Vec2((v.x + 1) * 0.5 * w, (-v.y + 1) * 0.5 * h); + if (from == 0) return new Vec2((v.x + 1) * 0.5 * w, (-v.y + 1) * 0.5 * h); + else if (from == 1) return (v.x + 1) * 0.5 * w; + else return (-v.y + 1) * 0.5 * h; } } diff --git a/leenkx/Sources/leenkx/logicnode/WriteGifNode.hx b/leenkx/Sources/leenkx/logicnode/WriteGifNode.hx new file mode 100644 index 00000000..dfd38e37 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/WriteGifNode.hx @@ -0,0 +1,185 @@ +package leenkx.logicnode; + +import iron.object.CameraObject; +import kha.Color; + +import iron.format.gif.GifEncoder; + +class WriteGifNode extends LogicNode { + + var file: String; + var camera: CameraObject; + var renderTarget: kha.Image; + + var width: Int; + var height: Int; + var tx: Int; + var ty: Int; + var tw: Int; + var th: Int; + + var r2d: Bool; + + var encoder: iron.format.gif.GifEncoder; + var frames: Array; + var bo: haxe.io.BytesOutput; + var fdur: Float; + + var duration: Float; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + if (from == 0){ + file = inputs[2].get(); + camera = inputs[3].get(); + + width = inputs[4].get(); + height = inputs[5].get(); + tx = inputs[6].get(); + ty = inputs[7].get(); + tw = inputs[8].get(); + th = inputs[9].get(); + r2d = inputs[10].get(); + + fdur = inputs[11].get(); + + renderTarget = kha.Image.createRenderTarget(width, height, + kha.graphics4.TextureFormat.RGBA32, + kha.graphics4.DepthStencilFormat.NoDepthAndStencil); + + frames = []; + bo = new haxe.io.BytesOutput(); + duration = 0.0; + + encoder = new iron.format.gif.GifEncoder(tw, th, fdur, -1, 10); + + encoder.start(bo); + + tree.notifyOnRender(render); + + } + else{ + + if (frames != null){ + + encoder.commit(bo); + + #if kha_krom + Krom.fileSaveBytes(Krom.getFilesLocation() + "/" + file, bo.getBytes().getData()); + + #elseif kha_html5 + var blob = new js.html.Blob([bo.getBytes().getData()], {type: "application"}); + var url = js.html.URL.createObjectURL(blob); + var a = cast(js.Browser.document.createElement("a"), js.html.AnchorElement); + a.href = url; + a.download = file; + a.click(); + js.html.URL.revokeObjectURL(url); + #end + + runOutput(0); + + tree.removeRender(render); + + renderTarget.unload(); + frames = null; + bo = null; + } + + } + + } + + function render(g: kha.graphics4.Graphics) { + + duration += iron.system.Time.delta; + + if (duration < fdur) + return; + + duration = 0; + + var ready = false; + final sceneCam = iron.Scene.active.camera; + final oldRT = camera.renderTarget; + + iron.Scene.active.camera = camera; + camera.renderTarget = renderTarget; + + camera.renderFrame(g); + + var tex = camera.renderTarget; + + camera.renderTarget = oldRT; + iron.Scene.active.camera = sceneCam; + + if (r2d){ + + tex = kha.Image.createRenderTarget(width, height, + kha.graphics4.TextureFormat.RGBA32, + kha.graphics4.DepthStencilFormat.NoDepthAndStencil); + + tex.g2.begin(true, Color.Transparent); + + tex.g2.color = Color.White; + tex.g2.drawScaledImage(renderTarget, 0, 0, width, height); + + var scl = width/ iron.App.w(); + + if (kha.Image.renderTargetsInvertedY()){ + tex.g2.scale(scl, -scl); + tex.g2.translate(0, height); + } + else + tex.g2.scale(scl, scl); + + for (f in @:privateAccess iron.App.traitRenders2D){ + f(tex.g2); + } + + tex.g2.end(); + + } + + var pixels = tex.getPixels(); + + for (i in 0...pixels.length){ + if (pixels.get(i) != 0){ ready = true; break; } + } + + if (ready) { + + var rgb = new haxe.io.UInt8Array(tw * th * 3); + for (j in ty...ty + th) { + for (i in tx...tx + tw) { + var k = j * tex.width + i; + var m = (j - ty) * tw + i - tx; + + #if kha_krom + var l = k; + #elseif kha_html5 + var l = (tex.height - j) * tex.width + i; + #end + + rgb.set(m * 3 + 0, pixels.get(l * 4 + 0)); + rgb.set(m * 3 + 1, pixels.get(l * 4 + 1)); + rgb.set(m * 3 + 2, pixels.get(l * 4 + 2)); + } + } + + var frame: GifFrame = { + delay: fdur, + flippedY: false, + data: rgb + } + + encoder.add(bo, frame); + + } + + } + +} diff --git a/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx b/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx index 22c1a9ef..87ebf7f5 100644 --- a/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx +++ b/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx @@ -9,6 +9,9 @@ class WriteImageNode extends LogicNode { var camera: CameraObject; var renderTarget: kha.Image; + var height: Int; + var width: Int; + public function new(tree: LogicTree) { super(tree); } @@ -17,10 +20,13 @@ class WriteImageNode extends LogicNode { // Relative or absolute path to file file = inputs[1].get(); - assert(Error, iron.App.w() % inputs[3].get() == 0 && iron.App.h() % inputs[4].get() == 0, "Aspect ratio must match display resolution ratio"); + //assert(Error, iron.App.w() % inputs[3].get() == 0 && iron.App.h() % inputs[4].get() == 0, "Aspect ratio must match display resolution ratio"); camera = inputs[2].get(); - renderTarget = kha.Image.createRenderTarget(inputs[3].get(), inputs[4].get(), + width = inputs[3].get(); + height = inputs[4].get(); + + renderTarget = kha.Image.createRenderTarget(width, height, kha.graphics4.TextureFormat.RGBA32, kha.graphics4.DepthStencilFormat.NoDepthAndStencil); @@ -46,20 +52,20 @@ class WriteImageNode extends LogicNode { if (inputs[9].get()){ - tex = kha.Image.createRenderTarget(inputs[3].get(), inputs[4].get(), + tex = kha.Image.createRenderTarget(width, height, kha.graphics4.TextureFormat.RGBA32, kha.graphics4.DepthStencilFormat.NoDepthAndStencil); tex.g2.begin(true, Color.Transparent); tex.g2.color = Color.White; - tex.g2.drawScaledImage(renderTarget, 0, 0, inputs[3].get(), inputs[4].get()); + tex.g2.drawScaledImage(renderTarget, 0, 0, width, height); - var scl = inputs[3].get() / iron.App.w(); + var scl = width / iron.App.w(); if (kha.Image.renderTargetsInvertedY()){ tex.g2.scale(scl, -scl); - tex.g2.translate(0, inputs[4].get()); + tex.g2.translate(0, height); } else tex.g2.scale(scl, scl); @@ -107,8 +113,11 @@ class WriteImageNode extends LogicNode { } } - var imgwriter = new iron.format.bmp.Writer(bo); - imgwriter.write(iron.format.bmp.Tools.buildFromARGB(tw, th, rgb)); + var writer = new iron.format.jpg.Writer(bo); + writer.write({width: tw, height: th, quality: 100, pixels: rgb}); + + //var imgwriter = new iron.format.bmp.Writer(bo); + //imgwriter.write(iron.format.bmp.Tools.buildFromARGB(tw, th, rgb)); #if kha_krom Krom.fileSaveBytes(Krom.getFilesLocation() + "/" + file, bo.getBytes().getData()); diff --git a/leenkx/Sources/leenkx/logicnode/WriteJsonNode.hx b/leenkx/Sources/leenkx/logicnode/WriteJsonNode.hx index b3b46ce2..28d11b36 100644 --- a/leenkx/Sources/leenkx/logicnode/WriteJsonNode.hx +++ b/leenkx/Sources/leenkx/logicnode/WriteJsonNode.hx @@ -10,7 +10,8 @@ class WriteJsonNode extends LogicNode { // Relative or absolute path to file var file: String = inputs[1].get(); var data: Dynamic = inputs[2].get(); - var s = haxe.Json.stringify(data); + //var s = haxe.Json.stringify(data); + var s = haxe.Json.stringify(data, "\t"); #if kha_krom var path = Krom.getFilesLocation() + "/" + file; diff --git a/leenkx/Sources/leenkx/math/Generator.hx b/leenkx/Sources/leenkx/math/Generator.hx new file mode 100644 index 00000000..4225fb5c --- /dev/null +++ b/leenkx/Sources/leenkx/math/Generator.hx @@ -0,0 +1,464 @@ +package leenkx.math; + +import iron.math.Vec4; + +// https://github.com/whuop/hxNoise/blob/master/hxnoise/Perlin.hx +// The MIT License (MIT) + +// Copyright (c) 2016 Kristian Brodal + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +class Perlin { + private static var PERMUTATION:Array = [ + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, + 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, + 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, + 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, + 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, + 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, + 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, + 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, + 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, + 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, + 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, + 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 + ]; + + private static var P:Array; + public var repeat(default, default):Int; + + public function new(repeat:Int = -1):Void { + this.repeat = repeat; + if (P == null) { + P = [for (x in 0...512) PERMUTATION[x % 256]]; + } + } + + public function perlin(x:Float, y:Float, z:Float):Float { + if (this.repeat > 0) { + x = x % repeat; + y = y % repeat; + z = z % repeat; + } + + var xi:Int = Math.floor(x) & 255; + var yi:Int = Math.floor(y) & 255; + var zi:Int = Math.floor(z) & 255; + + var xf:Float = x - Math.ffloor(x); + var yf:Float = y - Math.ffloor(y); + var zf:Float = z - Math.ffloor(z); + + var u:Float = fade(xf); + var v:Float = fade(yf); + var w:Float = fade(zf); + + var aaa, aab, aba, abb, baa, bab, bba, bbb:Int; + aaa = P[P[P[xi] + yi] + zi]; + aab = P[P[P[xi] + inc(yi)] + zi]; + aba = P[P[P[xi] + yi] + inc(zi)]; + abb = P[P[P[xi] + inc(yi)] + inc(zi)]; + baa = P[P[P[inc(xi)] + yi] + zi]; + bba = P[P[P[inc(xi)] + inc(yi)] + zi]; + bab = P[P[P[inc(xi)] + yi] + inc(zi)]; + bbb = P[P[P[inc(xi)] + inc(yi)] + inc(zi)]; + + var x1, x2, y1, y2:Float; + x1 = lerp(grad(aaa, xf, yf, zf), grad(baa, xf - 1, yf, zf), u); + x2 = lerp(grad(aba, xf, yf - 1, zf), grad(bba, xf - 1, yf - 1, zf), u); + y1 = lerp(x1, x2, v); + + x1 = lerp(grad(aab, xf, yf, zf - 1), grad(bab, xf - 1, yf, zf - 1), u); + x2 = lerp(grad(abb, xf, yf - 1, zf - 1), grad(bbb, xf - 1, yf - 1, zf - 1), u); + y2 = lerp(x1, x2, v); + + return (lerp(y1, y2, w) + 1) / 2; + } + + public function octavePerlin(x:Float, y:Float, z:Float, octaves:Int, persistence:Float, frequency:Float):Float { + var total:Float = 0.0; + var maxValue:Float = 0.0; + var amplitude:Float = 1.0; + + for (i in 0...octaves) { + total += perlin(x * frequency, y * frequency, z * frequency) * amplitude; + maxValue += amplitude; + + amplitude *= persistence; + frequency *= 2.0; + } + + return total / maxValue; + } + + public function fade(t:Float):Float { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + public function inc(num:Int):Int { + num++; + if (repeat > 0) num %= repeat; + return num; + } + + public static function grad(hash:Int, x:Float, y:Float, z:Float):Float { + var h:Int = hash & 15; + var u:Float = h < 8 ? x : y; + + var v:Float; + + if (h < 4) + v = y; + else if (h == 12 || h == 14) + v = x; + else + v = z; + + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + public function lerp(a:Float, b:Float, x:Float):Float { + return a + (x * (b - a)); + } +} + +class DiamondSquare { + private var m_values:Array; + private var m_width:Int; + private var m_height:Int; + private var m_featureSize:Int; + private var m_scale:Float; + private var m_randFunc:Void->Float; + + public function new(width:Int, height:Int, featureSize:Int, scale:Float, randFunc:Void->Float):Void { + m_width = width; + m_height = height; + m_featureSize = featureSize; + m_scale = scale; + m_randFunc = randFunc; + + var totalSize = m_width * m_height; + m_values = new Array(); + for (i in 0...totalSize) { + m_values.push(0.0); + } + + var x = 0; + while (x < m_width) { + var y = 0; + while (y < m_height) { + setValue(x, y, m_randFunc()); + y += m_featureSize; + } + x += m_featureSize; + } + } + + public function sampleSquare(x:Int, y:Int, size:Int, value:Float):Void { + var hs:Int = Math.floor(size / 2); + + var a = getValue(x - hs, y - hs); + var b = getValue(x + hs, y - hs); + var c = getValue(x - hs, y + hs); + var d = getValue(x + hs, y + hs); + + setValue(x, y, ((a + b + c + d) / 4.0) + value); + } + + public function sampleDiamond(x:Int, y:Int, size:Int, value:Float):Void { + var hs:Int = Math.floor(size / 2); + + var a = getValue(x - hs, y); + var b = getValue(x + hs, y); + var c = getValue(x, y - hs); + var d = getValue(x, y + hs); + + setValue(x, y, ((a + b + c + d) / 4.0) + value); + } + + public function diamondSquare():Void { + var stepSize:Int = m_featureSize; + var scale:Float = m_scale; + + while (stepSize > 1) { + var halfStep:Int = Math.floor(stepSize / 2); + + var y = halfStep; + while (y < m_height) { + var x = halfStep; + while (x < m_width) { + sampleSquare(x, y, stepSize, m_randFunc() * scale); + x += stepSize; + } + y += stepSize; + } + + y = 0; + while (y < m_height) { + var x = (y % stepSize == 0) ? halfStep : 0; + while (x < m_width) { + sampleDiamond(x, y, stepSize, m_randFunc() * scale); + x += stepSize; + } + y += halfStep; + } + + stepSize = Math.floor(stepSize / 2); + scale /= 2.0; + } + } + + public function getValue(x:Int, y:Int):Float { + x = wrap(x, m_width); + y = wrap(y, m_height); + return m_values[y * m_width + x]; + } + + public function setValue(x:Int, y:Int, value:Float):Void { + x = wrap(x, m_width); + y = wrap(y, m_height); + m_values[y * m_width + x] = value; + } + + private function wrap(v:Int, maxLength:Int):Int { + var wrapped = v % (maxLength - 1); + if (wrapped < 0) { + wrapped += (maxLength - 1); + } + return wrapped; + } +} + +//source from https://gist.github.com/anissen/e27e1a769be7b3bc46d55e50f3b11e92 + +// Adapted from java source by Herman Tulleken +// http://www.luma.co.za/labs/2008/02/27/poisson-disk-sampling/ + +// The algorithm is from the "Fast Poisson Disk Sampling in Arbitrary Dimensions" paper by Robert Bridson +// http://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf + +// Code adapted from http://theinstructionlimit.com/fast-uniform-poisson-disk-sampling-in-c + +typedef Settings = { + TopLeft: Vec4, + LowerRight: Vec4, + Dimensions: Vec4, + Center: Vec4, + CellSize: Float, + MinimumDistance: Float, + RejectionSqDistance: Null, + GridWidth: Int, + GridHeight: Int, + GridDepth: Int, + Is3D: Bool +} + +typedef State = { + Grid: Array>>, + ActivePoints: Array, + Points: Array +} + +class UniformPoissonSampler { + static public var DefaultPointsPerIteration: Int = 30; + static var SquareRootThree: Float = Math.sqrt(3); + static var SquareRootTwo: Float = Math.sqrt(2); + + public static function SampleCube(topLeft: Vec4, lowerRight: Vec4, minimumDistance: Float, pointsPerIteration: Int = 30): Array { + return Sample(topLeft, lowerRight, null, minimumDistance, pointsPerIteration, true); + } + + public static function SampleSphere(center: Vec4, radius: Float, minimumDistance: Float, pointsPerIteration: Int = 30): Array { + var topLeft = new Vec4(center.x - radius, center.y - radius, center.z - radius); + var lowerRight = new Vec4(center.x + radius, center.y + radius, center.z + radius); + return Sample(topLeft, lowerRight, radius, minimumDistance, pointsPerIteration, true); + } + + public static function SampleRectangle(topLeft: Vec4, lowerRight: Vec4, minimumDistance: Float, pointsPerIteration: Int = 30): Array { + return Sample(topLeft, lowerRight, null, minimumDistance, pointsPerIteration, false); + } + + public static function SampleCircle(center: Vec4, radius: Float, minimumDistance: Float, pointsPerIteration: Int = 30): Array { + var topLeft = new Vec4(center.x - radius, center.y - radius, center.z); + var lowerRight = new Vec4(center.x + radius, center.y + radius, center.z); + return Sample(topLeft, lowerRight, radius, minimumDistance, pointsPerIteration, false); + } + + static function Sample(topLeft: Vec4, lowerRight: Vec4, rejectionDistance: Null, minimumDistance: Float, pointsPerIteration: Int, is3D: Bool): Array { + var dimensions = new Vec4(lowerRight.x - topLeft.x, lowerRight.y - topLeft.y, lowerRight.z - topLeft.z); + var cellSize = is3D ? minimumDistance / SquareRootThree : minimumDistance / SquareRootTwo; + + var settings: Settings = { + TopLeft: topLeft, + LowerRight: lowerRight, + Dimensions: dimensions, + Center: new Vec4((topLeft.x + lowerRight.x) / 2, (topLeft.y + lowerRight.y) / 2, (topLeft.z + lowerRight.z) / 2), + CellSize: cellSize, + MinimumDistance: minimumDistance, + RejectionSqDistance: rejectionDistance == null ? null : rejectionDistance * rejectionDistance, + GridWidth: Std.int(dimensions.x / cellSize) + 1, + GridHeight: Std.int(dimensions.y / cellSize) + 1, + GridDepth: is3D ? Std.int(dimensions.z / cellSize) + 1 : 1, + Is3D: is3D + }; + + var grid = []; + for (z in 0 ... settings.GridDepth) { + var plane = []; + for (y in 0 ... settings.GridHeight) { + var row = []; + for (x in 0 ... settings.GridWidth) { + row.push(null); + } + plane.push(row); + } + grid.push(plane); + } + + var state: State = { + Grid: grid, + ActivePoints: [], + Points: [] + }; + + AddFirstPoint(settings, state); + + while (state.ActivePoints.length != 0) { + var ArrayIndex: Int = Math.floor(Math.random() * state.ActivePoints.length); + + var point = state.ActivePoints[ArrayIndex]; + var found = false; + + for (k in 0 ... pointsPerIteration) { + if (AddNextPoint(point, settings, state)) found = true; + } + + if (!found) state.ActivePoints.splice(ArrayIndex, 1); + } + + return state.Points; + } + + static function DistanceSquared(p1: Vec4, p2: Vec4) { + var dx = p1.x - p2.x; + var dy = p1.y - p2.y; + var dz = p1.z - p2.z; + return (dx * dx) + (dy * dy) + (dz * dz); + } + + static function Distance(p1: Vec4, p2: Vec4) { + return Math.sqrt(DistanceSquared(p1, p2)); + } + + static function AddFirstPoint(settings: Settings, state: State) { + var added = false; + while (!added) { + var xr = settings.TopLeft.x + settings.Dimensions.x * Math.random(); + var yr = settings.TopLeft.y + settings.Dimensions.y * Math.random(); + var zr = settings.Is3D ? settings.TopLeft.z + settings.Dimensions.z * Math.random() : settings.TopLeft.z; + + var p = new Vec4(xr, yr, zr); + + if (settings.RejectionSqDistance != null && DistanceSquared(settings.Center, p) > settings.RejectionSqDistance) + continue; + added = true; + + var index = Denormalize(p, settings.TopLeft, settings.CellSize, settings.Is3D); + + state.Grid[Std.int(index.z)][Std.int(index.y)][Std.int(index.x)] = p; + + state.ActivePoints.push(p); + state.Points.push(p); + } + } + + static function AddNextPoint(point: Vec4, settings: Settings, state: State): Bool { + var found = false; + var q = GenerateRandomAround(point, settings.MinimumDistance, settings.Is3D); + + if (q.x >= settings.TopLeft.x && q.x < settings.LowerRight.x && + q.y >= settings.TopLeft.y && q.y < settings.LowerRight.y && + (!settings.Is3D || (q.z >= settings.TopLeft.z && q.z < settings.LowerRight.z)) && + (settings.RejectionSqDistance == null || DistanceSquared(settings.Center, q) <= settings.RejectionSqDistance)) + { + var qIndex = Denormalize(q, settings.TopLeft, settings.CellSize, settings.Is3D); + var tooClose = false; + + for (i in Std.int(Math.max(0, qIndex.x - 2)) ... Std.int(Math.min(settings.GridWidth, qIndex.x + 3))){ + for (j in Std.int(Math.max(0, qIndex.y - 2)) ... Std.int(Math.min(settings.GridHeight, qIndex.y + 3))) { + for (l in Std.int(Math.max(0, qIndex.z - 2)) ... Std.int(Math.min(settings.GridDepth, qIndex.z + 3))) { + + var neighbor = state.Grid[l][j][i]; + + if (neighbor != null && Distance(neighbor, q) < settings.MinimumDistance) { + tooClose = true; + break; + } + } + if (tooClose) break; + } + if (tooClose) break; + } + + if (!tooClose) { + found = true; + state.ActivePoints.push(q); + state.Points.push(q); + state.Grid[Std.int(qIndex.z)][Std.int(qIndex.y)][Std.int(qIndex.x)] = q; + } + } + return found; + } + + static function GenerateRandomAround(center: Vec4, minimumDistance: Float, is3D: Bool): Vec4 { + var radiusMin = minimumDistance; + var radiusMax = minimumDistance * 2; + var radius = radiusMin + (radiusMax - radiusMin) * Math.random(); + + if (is3D) { + var d1 = Math.random(); + var d2 = Math.random(); + var phi = Math.PI * 2 * d1; + var theta = Math.acos(1 - 2 * d2); + var sinTheta = Math.sin(theta); + var x = radius * sinTheta * Math.cos(phi); + var y = radius * sinTheta * Math.sin(phi); + var z = radius * Math.cos(theta); + return new Vec4(center.x + x, center.y + y, center.z + z); + } else { + var angle = Math.PI * 2 * Math.random(); + var x = radius * Math.cos(angle); + var y = radius * Math.sin(angle); + return new Vec4(center.x + x, center.y + y, center.z); + } + } + + static function Denormalize(point: Vec4, origin: Vec4, cellSize: Float, is3D: Bool): Vec4 { + return new Vec4( + (point.x - origin.x) / cellSize, + (point.y - origin.y) / cellSize, + is3D ? (point.z - origin.z) / cellSize : 0 + ); + } +} diff --git a/leenkx/Sources/leenkx/object/BreakerExtension.hx b/leenkx/Sources/leenkx/object/BreakerExtension.hx new file mode 100644 index 00000000..d9838f5a --- /dev/null +++ b/leenkx/Sources/leenkx/object/BreakerExtension.hx @@ -0,0 +1,554 @@ +package leenkx.object; + +import iron.math.Vec4; +import iron.math.Mat4; +import iron.object.MeshObject; +import leenkx.object.MeshDataExtension; +import iron.Scene; + +// Based on work by yomboprime https://github.com/yomboprime +// This class can be used to subdivide a convex geometry object into pieces +class ConvexBreaker { + + var minSizeForBreak: Float; + var smallDelta: Float; + + var tempLine: Line3; + var tempPlane: Plane; + var tempPlane2: Plane; + var tempCM1: Vec4; + var tempCM2: Vec4; + var tempVec4: Vec4; + var tempVec42: Vec4; + var tempVec43: Vec4; + var tempCutResult: CutResult; + var segments: Array; + + var scaleUV: Float = 0.3; + var flatShading: Bool = true; + + public var userDataMap: Map; + + // minSizeForBreak Min size a debris can have to break + // smallDelta Max distance to consider that a point belongs to a plane + public function new(minSizeForBreak = 1.4, smallDelta = 0.0001) { + this.minSizeForBreak = minSizeForBreak; + this.smallDelta = smallDelta; + tempLine = new Line3(); + tempPlane = new Plane(); + tempPlane2 = new Plane(); + tempCM1 = new Vec4(); + tempCM2 = new Vec4(); + tempVec4 = new Vec4(); + tempVec42 = new Vec4(); + tempVec43 = new Vec4(); + tempCutResult = new CutResult(); + segments = new Array(); + var n = 30 * 30; + for (i in 0...n) segments.push(false); + userDataMap = new Map(); + } + + public function initBreakableObject(object: MeshObject, mass: Float, friction: Float, velocity: Vec4, angularVelocity: Vec4, breakable: Bool) { + var ar = object.data.geom.positions.values; + var scalePos = object.data.scalePos; + // Create vertices mark + var sc = object.transform.scale; + var vertices = new Array(); + for (i in 0...Std.int(ar.length / 4)) { + // Use w component as mark + vertices.push(new Vec4( + ar[i * 4 ] * sc.x * (1 / 32767) * scalePos, + ar[i * 4 + 1] * sc.y * (1 / 32767) * scalePos, + ar[i * 4 + 2] * sc.z * (1 / 32767) * scalePos, + 0 + )); + } + + var faces = new Array(); + + for (materialIndex in 0...object.data.geom.indices.length) { + var ind = object.data.geom.indices[materialIndex]; + for (i in 0...Std.int(ind.length / 3)) { + var a = ind[i * 3]; + var b = ind[i * 3 + 1]; + var c = ind[i * 3 + 2]; + // Merge duplis + for (f in faces) { + if (vertices[a].equals(vertices[f.a])) a = f.a; + else if (vertices[a].equals(vertices[f.b])) a = f.b; + else if (vertices[a].equals(vertices[f.c])) a = f.c; + if (vertices[b].equals(vertices[f.a])) b = f.a; + else if (vertices[b].equals(vertices[f.b])) b = f.b; + else if (vertices[b].equals(vertices[f.c])) b = f.c; + if (vertices[c].equals(vertices[f.a])) c = f.a; + else if (vertices[c].equals(vertices[f.b])) c = f.b; + else if (vertices[c].equals(vertices[f.c])) c = f.c; + } + faces.push(new Face3(a, b, c)); + } + } + + // Reorder vertices + var verts = new Array(); + var map = new Map(); + var i = 0; + function orderVert(fi: Int): Int { + var val = map.get(fi); + if (val == null) { + verts.push(vertices[fi]); + map.set(fi, i); + i++; + return i - 1; + } + else return val; + } + for (f in faces) { + f.a = orderVert(f.a); + f.b = orderVert(f.b); + f.c = orderVert(f.c); + } + + var userData = new UserData(); + userData.mass = mass; + userData.friction = friction; + userData.velocity = velocity.clone(); + userData.angularVelocity = angularVelocity.clone(); + userData.breakable = breakable; + userData.vertices = verts; + userData.faces = faces; + userDataMap.set(object, userData); + } + + public function subdivideByPlane(object: MeshObject, plane: Vec4 = null, random: Bool = true): Array { + var debris: Array = []; + var scope = this; + + if (plane == null) plane = new Vec4(1, 1, 1); + + if (random) + tempPlane2.normal.setFrom(new Vec4( + plane.x > 0 ? Math.random() - 0.5 : plane.x, + plane.y > 0 ? Math.random() - 0.5 : plane.y, + plane.z > 0 ? Math.random() - 0.5 : plane.z + ).normalize()); + else + tempPlane2.normal.setFrom(new Vec4(plane.x, plane.y, plane.z).normalize()); + + tempPlane2.constant = -(object.transform.loc.dot(tempPlane2.normal)); + + scope.cutByPlane(object, tempPlane2, scope.tempCutResult); + + var object1 = scope.tempCutResult.object1; + var object2 = scope.tempCutResult.object2; + + if (object1 != null) debris.push(object1); + if (object2 != null) debris.push(object2); + + Scene.active.meshes.remove(object); + + return debris; + } + + // maxRadialIterations Iterations for radial cuts + // maxRandomIterations Max random iterations for not-radial cuts + public function subdivideByImpact(object: MeshObject, pointOfImpact: Vec4, normal: Vec4, maxRadialIterations: Int, maxRandomIterations: Int): Array { + var debris: Array = []; + + tempVec4.addvecs(pointOfImpact, normal); + tempPlane.setFromCoplanarPoints(pointOfImpact, object.transform.loc, tempVec4); + + var maxTotalIterations = maxRandomIterations + maxRadialIterations; + var scope = this; + + function subdivideRadial(subObject: MeshObject, startAngle: Float, endAngle: Float, numIterations: Int) { + + if (Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations) { + debris.push(subObject); + return; + } + + var angle = Math.PI; + if (numIterations == 0) { + tempPlane2.normal.setFrom(tempPlane.normal); + tempPlane2.constant = tempPlane.constant; + } + else { + if (numIterations <= maxRadialIterations) { + angle = (endAngle - startAngle) * (0.2 + 0.6 * Math.random()) + startAngle; + + // Rotate tempPlane2 at impact point around normal axis and the angle + scope.tempVec42.setFrom(object.transform.loc).sub(pointOfImpact).applyAxisAngle(normal, angle).add(pointOfImpact); + tempPlane2.setFromCoplanarPoints(pointOfImpact, scope.tempVec4, scope.tempVec42); + } + else { + angle = ((0.5 * (numIterations & 1)) + 0.2 * (2 - Math.random())) * Math.PI; + + // Rotate tempPlane2 at object position around normal axis and the angle + scope.tempVec42.setFrom(pointOfImpact).sub(subObject.transform.loc).applyAxisAngle(normal, angle).add(subObject.transform.loc); + scope.tempVec43.setFrom(normal).add(subObject.transform.loc); + tempPlane2.setFromCoplanarPoints(subObject.transform.loc, scope.tempVec43, scope.tempVec42); + } + } + + // Perform the cut + scope.cutByPlane(subObject, tempPlane2, scope.tempCutResult); + + var object1 = scope.tempCutResult.object1; + var object2 = scope.tempCutResult.object2; + if (object1 != null) subdivideRadial(object1, startAngle, angle, numIterations + 1); + if (object2 != null) subdivideRadial(object2, angle, endAngle, numIterations + 1); + + // Object was subdivided into debris + Scene.active.meshes.remove(subObject); + } + + subdivideRadial(object, 0, 2 * Math.PI, 0); + return debris; + } + + function transformFreeVectorInverse(v: Vec4, m: Mat4): Vec4 { + var x = v.x, y = v.y, z = v.z; + v.x = m._00 * x + m._10 * y + m._20 * z; + v.y = m._01 * x + m._11 * y + m._21 * z; + v.z = m._02 * x + m._12 * y + m._22 * z; + return v; + } + + function transformTiedVectorInverse(v: Vec4, m: Mat4): Vec4 { + var x = v.x - m._30, y = v.y - m._31, z = v.z - m._32; + v.x = m._00 * x + m._10 * y + m._20 * z; + v.y = m._01 * x + m._11 * y + m._21 * z; + v.z = m._02 * x + m._12 * y + m._22 * z; + return v; + } + + function transformPlaneToLocalSpace(plane: Plane, m: Mat4, resultPlane: Plane) { + var v1 = new Vec4(); + var referencePoint = transformTiedVectorInverse(plane.coplanarPoint(v1), m); + resultPlane.normal.setFrom(plane.normal); + transformFreeVectorInverse(resultPlane.normal, m); + resultPlane.normal.normalize(); + resultPlane.constant = -referencePoint.dot(resultPlane.normal); + } + + // Returns breakable objects, the resulting 2 pieces of the cut + // object2 can be null if the plane doesn't cut the object + // object1 can be null only in case of error + // Returned value is number of pieces, 0 for error + function cutByPlane(object: MeshObject, plane: Plane, output: CutResult): Int { + var userData = userDataMap.get(object); + var points: Array = userData.vertices; + var faces: Array = userData.faces; + + var numPoints = points.length; + var points1 = []; + var points2 = []; + var delta = smallDelta; + + // Reset vertices mark + for (i in 0...numPoints) points[i].w = 0; + + // Reset segments mark + var numPointPairs = numPoints * numPoints; + for (i in 0...numPointPairs) this.segments[i] = false; + + // Iterate through the faces to mark edges shared by coplanar faces + for (i in 0...faces.length - 1) { + var face1 = faces[i]; + + for (j in (i + 1)...faces.length) { + var face2 = faces[j]; + var coplanar = 1 - face1.normal.dot(face2.normal) < delta; + + if (coplanar) { + var a1 = face1.a; + var b1 = face1.b; + var c1 = face1.c; + var a2 = face2.a; + var b2 = face2.b; + var c2 = face2.c; + + if (a1 == a2 || a1 == b2 || a1 == c2) { + if (b1 == a2 || b1 == b2 || b1 == c2) { + this.segments[a1 * numPoints + b1] = true; + this.segments[b1 * numPoints + a1] = true; + } + else { + this.segments[c1 * numPoints + a1] = true; + this.segments[a1 * numPoints + c1] = true; + } + } + else if (b1 == a2 || b1 == b2 || b1 == c2) { + this.segments[c1 * numPoints + b1] = true; + this.segments[b1 * numPoints + c1] = true; + } + } + } + } + + // Transform the plane to object local space + var localPlane = this.tempPlane; + object.transform.buildMatrix(); + transformPlaneToLocalSpace(plane, object.transform.world, localPlane); + + // Iterate through the faces adding points to both pieces + for (i in 0...faces.length) { + + var face = faces[i]; + for (segment in 0...3) { + var i0 = segment == 0 ? face.a : (segment == 1 ? face.b : face.c); + var i1 = segment == 0 ? face.b : (segment == 1 ? face.c : face.a); + + var segmentState = this.segments[i0 * numPoints + i1]; + // The segment already has been processed in another face + if (segmentState) continue; + + // Mark segment as processed (also inverted segment) + this.segments[i0 * numPoints + i1] = true; + this.segments[i1 * numPoints + i0] = true; + + var p0 = points[i0]; + var p1 = points[i1]; + + if (p0.w == 0) { + var d = localPlane.distanceToPoint(p0); + + // mark: 1 for negative side, 2 for positive side, 3 for coplanar point + if (d > delta) { + p0.w = 2; + points2.push(p0); + } + else if (d < -delta) { + p0.w = 1; + points1.push(p0); + } + else { + p0.w = 3; + points1.push(p0); + var p02 = p0.clone(); + p02.w = 3; + points2.push(p02); + } + } + + if (p1.w == 0) { + var d = localPlane.distanceToPoint(p1); + + // mark: 1 for negative side, 2 for positive side, 3 for coplanar point + if (d > delta) { + p1.w = 2; + points2.push(p1); + } + else if (d < -delta) { + p1.w = 1; + points1.push(p1); + } + else { + p1.w = 3; + points1.push(p1); + var p1_2 = p1.clone(); + p1_2.w = 3; + points2.push(p1_2); + } + } + + var mark0 = p0.w; + var mark1 = p1.w; + + if ((mark0 == 1 && mark1 == 2 ) || ( mark0 == 2 && mark1 == 1)) { + // Intersection of segment with the plane + tempLine.start.setFrom(p0); + tempLine.end.setFrom(p1); + var intersection = localPlane.intersectLine(tempLine); + if (intersection == null) return 0; + + intersection.w = 1; + points1.push(intersection); + var intersection_2 = intersection.clone(); + intersection_2.w = 2; + points2.push(intersection_2); + } + } + } + + // Calculate debris mass (very fast and precise): + var newMass = userData.mass * 0.5; + + tempCM1.set(0, 0, 0); + var radius1 = 0.0; + if (points1.length > 0) { + for (p in points1) + tempCM1.add(p); + tempCM1.mult(1.0 / points1.length); + for (p in points1) { + p.sub(tempCM1); radius1 = Math.max(radius1, p.length()); } + tempCM1.applymat4(object.transform.world); + } + + tempCM2.set(0, 0, 0); + var radius2 = 0.0; + if (points2.length > 0) { + for (p in points2) + tempCM2.add(p); + tempCM2.mult(1.0 / points2.length); + for (p in points2) { + p.sub(tempCM2); radius2 = Math.max(radius2, p.length()); } + tempCM2.applymat4(object.transform.world); + } + + //MATERIALES + var mats = object.materials; + var udParent = userDataMap.get(object); + if (udParent.matWeights == null) { + udParent.matWeights = []; + udParent.totalWeight = 0; + for (i in 0...object.data.geom.indices.length) { + var w = object.data.geom.indices[i].length; + udParent.matWeights.push(w); + udParent.totalWeight += w; + } + } + + function getWeightedIndex(): Int { + var r = Std.random(udParent.totalWeight); + var acc = 0; + for (i in 0...udParent.matWeights.length) { + acc += udParent.matWeights[i]; + if (r < acc) return i; + } + return 0; + } + + var object1 = null; + var object2 = null; + var numObjects = 0; + + if (points1.length > 4) { + var data1 = MeshDataExtension.makeMeshData(points1, scaleUV, flatShading); + if (data1 != null) { + var sel1 = getWeightedIndex(); + var mats1 = [mats[sel1]]; + for (i in 0...mats.length) if (i != sel1) mats1.push(mats[i]); + object1 = new MeshObject(data1, haxe.ds.Vector.fromArrayCopy(mats1)); + object1.transform.loc.setFrom(tempCM1); + object1.transform.rot.setFrom(object.transform.rot); + object1.transform.buildMatrix(); + initBreakableObject(object1, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius1 > minSizeForBreak); + userDataMap.get(object1).matWeights = udParent.matWeights; + userDataMap.get(object1).totalWeight = udParent.totalWeight; + numObjects++; + } + } + + if (points2.length > 4) { + var data2 = MeshDataExtension.makeMeshData(points2, scaleUV, flatShading); + if (data2 != null) { + var sel2 = getWeightedIndex(); + var mats2 = [mats[sel2]]; + for (i in 0...mats.length) if (i != sel2) mats2.push(mats[i]); + object2 = new MeshObject(data2, haxe.ds.Vector.fromArrayCopy(mats2)); + object2.transform.loc.setFrom(tempCM2); + object2.transform.rot.setFrom(object.transform.rot); + object2.transform.buildMatrix(); + initBreakableObject(object2, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius2 > minSizeForBreak); + userDataMap.get(object2).matWeights = udParent.matWeights; + userDataMap.get(object2).totalWeight = udParent.totalWeight; + numObjects++; + } + } + + output.object1 = object1; + output.object2 = object2; + return numObjects; + } +} + +class UserData { + + public var mass: Float; + public var friction: Float; + public var velocity: Vec4; + public var angularVelocity: Vec4; + public var breakable: Bool; + + public var vertices: Array; + public var faces: Array; + + public var matWeights: Array; + public var totalWeight: Int; + + public function new() {} +} + +class CutResult { + + public var object1: MeshObject = null; + public var object2: MeshObject = null; + public function new() {} +} + +class Line3 { + + public var start: Vec4; + public var end: Vec4; + + public function new() { + start = new Vec4(); + end = new Vec4(); + } + + public function delta(result: Vec4): Vec4 { + result.subvecs(end, start); + return result; + } +} + +class Plane { + + public var normal = new Vec4(1.0, 0.0, 0.0); + public var constant = 0.0; + + public function new() {} + + public function distanceToPoint(point: Vec4): Float { + return normal.dot(point) + constant; + } + + public function setFromCoplanarPoints(a: Vec4, b: Vec4, c: Vec4): Plane { + var v1 = new Vec4(); + var v2 = new Vec4(); + var normal = v1.subvecs(c, b).cross(v2.subvecs(a, b)).normalize(); + set(normal, a); + return this; + } + + public function set(normal: Vec4, point: Vec4): Plane { + this.normal.setFrom(normal); + constant = -point.dot(this.normal); + return this; + } + + public function coplanarPoint(result: Vec4): Vec4 { + return result.setFrom(normal).mult(-constant); + } + + public function intersectLine(line: Line3): Vec4 { + var v1 = new Vec4(); + var result = new Vec4(); + var direction = line.delta(v1); + var denominator = normal.dot(direction); + if (denominator == 0) { + // line is coplanar, return origin + if (distanceToPoint(line.start) == 0) { + return result.setFrom(line.start); + } + // Unsure if this is the correct method to handle this case. + return null; + } + + var t = -(line.start.dot(this.normal) + constant) / denominator; + if (t < 0 || t > 1) return null; + return result.setFrom(direction).mult(t).add(line.start); + } +} diff --git a/leenkx/Sources/leenkx/object/MeshDataExtension.hx b/leenkx/Sources/leenkx/object/MeshDataExtension.hx new file mode 100644 index 00000000..ce0c5d11 --- /dev/null +++ b/leenkx/Sources/leenkx/object/MeshDataExtension.hx @@ -0,0 +1,309 @@ +package leenkx.object; + +import iron.math.Vec4; +import iron.data.MeshData; +import iron.data.SceneFormat; + +class MeshDataExtension { + + static var meshIndex = 0; + static public function makeMeshData(points: Array, scaleUV: Float = 0.3, flatShading: Bool = true): MeshData { + // Need at least 4 points for a 3D hull + if (points.length < 4) return null; + + while (points.length > 50) points.pop(); + var cm = new ConvexHull(points); + + // Validate hull has enough geometry for a mesh + if (cm.vertices.length < 4 || cm.face3s.length < 4) return null; + + var maxdim = 1.0; + var pa = new Array(); + var na = new Array(); + for (p in cm.vertices) { + pa.push(p.x); + pa.push(p.y); + pa.push(p.z); + na.push(0.0); + na.push(0.0); + na.push(0.0); + + var ax = Math.abs(p.x); + var ay = Math.abs(p.y); + var az = Math.abs(p.z); + if (ax > maxdim) maxdim = ax; + if (ay > maxdim) maxdim = ay; + if (az > maxdim) maxdim = az; + } + maxdim *= 2; + + var ind = new Array(); + + if (flatShading) { + function addFlatNormal(normal: Vec4, fi: Int) { + if (na[fi * 3] != 0.0 || na[fi * 3 + 1] != 0.0 || na[fi * 3 + 2] != 0.0) { + pa.push(pa[fi * 3 ]); + pa.push(pa[fi * 3 + 1]); + pa.push(pa[fi * 3 + 2]); + na.push(normal.x); + na.push(normal.y); + na.push(normal.z); + ind.push(Std.int(pa.length / 3 - 1)); + } + else { + na[fi * 3 ] = normal.x; + na[fi * 3 + 1] = normal.y; + na[fi * 3 + 2] = normal.z; + ind.push(fi); + } + } + for (f in cm.face3s) { + // Duplicate vertex for flat normals + addFlatNormal(f.normal, f.a); + addFlatNormal(f.normal, f.b); + addFlatNormal(f.normal, f.c); + } + }else { + var vNormals = [for (i in 0...cm.vertices.length) new Vec4(0, 0, 0)]; + for (f in cm.face3s) { + vNormals[f.a].add(f.normal); + vNormals[f.b].add(f.normal); + vNormals[f.c].add(f.normal); + } + for (vn in vNormals) vn.normalize(); + na = []; + for (vn in vNormals) { + na.push(vn.x); + na.push(vn.y); + na.push(vn.z); + } + for (f in cm.face3s) { + ind.push(f.a); + ind.push(f.b); + ind.push(f.c); + } + } + + // TODO: + var n = Std.int(pa.length / 3); + var paa = new kha.arrays.Int16Array(n * 4); + var naa = new kha.arrays.Int16Array(n * 2); + var texa = new kha.arrays.Int16Array(n * 2); + var invdim = 1 / maxdim; + + var tanga = new kha.arrays.Int16Array(n * 4); + + for (i in 0...n) { + var px = pa[i * 3]; + var py = pa[i * 3 + 1]; + var pz = pa[i * 3 + 2]; + var nx = Math.abs(na[i * 3]); + var ny = Math.abs(na[i * 3 + 1]); + var nz = Math.abs(na[i * 3 + 2]); + + paa.set(i * 4, Std.int(px * 32767 * invdim)); + paa.set(i * 4 + 1, Std.int(py * 32767 * invdim)); + paa.set(i * 4 + 2, Std.int(pz * 32767 * invdim)); + naa.set(i * 2 , Std.int(na[i * 3 ] * 32767 * invdim)); + naa.set(i * 2 + 1, Std.int(na[i * 3 + 1] * 32767 * invdim)); + paa.set(i * 4 + 3, Std.int(na[i * 3 + 2] * 32767 * invdim)); + + var u: Float = 0; + var v: Float = 0; + + if (nx > ny && nx > nz) { + u = pz * invdim * scaleUV + 0.5; + v = py * invdim * scaleUV + 0.5; + } else if (ny > nx && ny > nz) { + u = px * invdim * scaleUV + 0.5; + v = pz * invdim * scaleUV + 0.5; + } else { + u = px * invdim * scaleUV + 0.5; + v = py * invdim * scaleUV + 0.5; + } + + texa.set(i * 2, Std.int(u * 32767)); + texa.set(i * 2 + 1, Std.int(v * 32767)); + + var tan = new Vec4(); + if (nx > ny && nx > nz) + tan.set(0, 0, 1); + else if (ny > nx && ny > nz) + tan.set(1, 0, 0); + else + tan.set(1, 0, 0); + tan.normalize(); + + tanga.set(i * 4, Std.int(tan.x * 32767)); + tanga.set(i * 4 + 1, Std.int(tan.y * 32767)); + tanga.set(i * 4 + 2, Std.int(tan.z * 32767)); + tanga.set(i * 4 + 3, 32767); + + } + var inda = new kha.arrays.Uint32Array(ind.length); + for (i in 0...ind.length) inda.set(i, ind[i]); + + var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; + var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; + var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; + var tang: TVertexArray = { attrib: "tang", values: tanga, data: "short4norm" }; + + var indices: TIndexArray = { material: 0, values: inda }; + + var rawmesh: TMeshData = { + name: "TempMesh" + (meshIndex++), + sorting_index: 0, + vertex_arrays: [pos, nor, tex, tang], + index_arrays: [indices], + scale_pos: maxdim + }; + + // Synchronous on Krom + var md = new MeshData(rawmesh, function(d: MeshData) {}); + md.geom.calculateAABB(); + return md; + } +} + +// Based on work by qiao https://github.com/qiao +// This is a convex hull generator using the incremental method +// The complexity is O(n^2) where n is the number of vertices +class ConvexHull { + + var faces = [[0, 1, 2], [0, 2, 1]]; + public var face3s = new Array(); + public var vertices = new Array(); + + public function new(vertices: Array) { + + for (i in 3...vertices.length) addPoint(i, vertices); + + // Push vertices into array, skipping those inside the hull + // Map from old vertex id to new id + var id = 0; + var newId = new Array(); + for (i in 0...vertices.length) newId.push(-1); + + for (i in 0...faces.length) { + var face = faces[i]; + for (j in 0...3) { + if (newId[face[j]] == -1) { + newId[face[j]] = id++; + this.vertices.push(vertices[face[j]]); + } + face[j] = newId[face[j]]; + } + } + + for (i in 0...faces.length) { + face3s.push(new Face3(faces[i][0], faces[i][1], faces[i][2])); + } + + computeFaceNormals(); + } + + var cb = new Vec4(); + var ab = new Vec4(); + function computeFaceNormals() { + for (f in 0...face3s.length) { + var face = face3s[f]; + var va = vertices[face.a]; + var vb = vertices[face.b]; + var vc = vertices[face.c]; + cb.subvecs(vc, vb); + ab.subvecs(va, vb); + cb.cross(ab); + cb.normalize(); + face.normal.setFrom(cb); + } + } + + function addPoint(vertexId: Int, vertices: Array) { + var vertex = vertices[vertexId].clone(); + + var mag = vertex.length(); + vertex.x += mag * randomOffset(); + vertex.y += mag * randomOffset(); + vertex.z += mag * randomOffset(); + + var hole: Array> = []; + var f = 0; + while (f < faces.length) { + var face = faces[f]; + + // For each face, if the vertex can see it, + // then we try to add the face's edges into the hole + if (visible(face, vertex, vertices)) { + for (e in 0...3) { + var edge = [face[e], face[(e + 1) % 3]]; + var boundary = true; + + // Remove duplicated edges + for (h in 0...hole.length) { + if (equalEdge(hole[h], edge)) { + hole[h] = hole[hole.length - 1]; + hole.pop(); + boundary = false; + break; + } + } + if (boundary) hole.push(edge); + } + + faces[f] = faces[faces.length - 1]; + faces.pop(); + } + else { + f++; + } + } + + // Construct the new faces formed by the edges of the hole and the vertex + for (h in 0...hole.length) { + faces.push([hole[h][0], hole[h][1], vertexId]); + } + } + + // Whether the face is visible from the vertex + function visible(face: Array, vertex: Vec4, vertices: Array): Bool { + var va = vertices[face[0]]; + var vb = vertices[face[1]]; + var vc = vertices[face[2]]; + var n = normal(va, vb, vc); + var dist = n.dot(va); // Distance from face to origin + return n.dot(vertex) >= dist; + } + + function normal(va: Vec4, vb: Vec4, vc: Vec4): Vec4 { + var cb = new Vec4(); + var ab = new Vec4(); + cb.subvecs(vc, vb); + ab.subvecs(va, vb); + cb.cross(ab); + cb.normalize(); + return cb; + } + + function equalEdge(ea: Array, eb: Array): Bool { + return ea[0] == eb[1] && ea[1] == eb[0]; + } + + function randomOffset(): Float { + return (Math.random() - 0.5) * 2 * 1e-6; + } +} + +class Face3 { + + public var a: Int; + public var b: Int; + public var c: Int; + public var normal: Vec4; + + public function new(a: Int, b: Int, c: Int) { + this.a = a; + this.b = b; + this.c = c; + normal = new Vec4(); + } +} diff --git a/leenkx/Sources/leenkx/renderpath/DynamicResolutionScale.hx b/leenkx/Sources/leenkx/renderpath/DynamicResolutionScale.hx index cb05f43a..3bf8ce24 100644 --- a/leenkx/Sources/leenkx/renderpath/DynamicResolutionScale.hx +++ b/leenkx/Sources/leenkx/renderpath/DynamicResolutionScale.hx @@ -4,49 +4,58 @@ import iron.RenderPath; class DynamicResolutionScale { + public static var enabled = true; public static var dynamicScale = 1.0; - static var firstFrame = true; - static inline var startScaleMs = 30; - static inline var scaleRangeMs = 10; - static inline var maxScale = 0.6; + static inline var targetMs = 30.0; + static inline var rangeMs = 10.0; + static inline var minScale = 0.6; + + static var lastTime = 0.0; + static var totalTime = 0.0; + static var frames = 0; + static var frameTimeAvg = 0.0; + static var needsResize = false; + static var renderSize: kha.graphics4.Graphics->Void = null; public static function run(path: RenderPath) { - if (firstFrame) { - iron.App.notifyOnRender(render); - firstFrame = false; + if (!enabled) { + if (dynamicScale != 1.0) { + dynamicScale = 1.0; + needsResize = true; + } return; } - // TODO: execute once per frame max - if (frameTimeAvg > startScaleMs && frameTimeAvg < 100) { - var overTime = Math.min(scaleRangeMs, frameTimeAvg - startScaleMs); - var scale = 1.0 - (overTime / scaleRangeMs) * (1.0 - maxScale); + var now = kha.Scheduler.realTime(); + totalTime += (now - lastTime); + lastTime = now; + frames++; - var w = Std.int(iron.App.w() * scale); - var h = Std.int(iron.App.h() * scale); - path.setCurrentViewport(w, h); - path.setCurrentScissor(w, h); - dynamicScale = scale; + if (totalTime >= 1.0) { + frameTimeAvg = (totalTime / frames) * 1000.0; + totalTime = 0.0; + frames = 0; + + var newScale = (frameTimeAvg > targetMs) ? + (1.0 - (Math.min(rangeMs, frameTimeAvg - targetMs) / rangeMs) * (1.0 - minScale)) : 1.0; + + if (newScale != dynamicScale) { + dynamicScale = newScale; + needsResize = true; + } } - else dynamicScale = 1.0; - } - static var frameTime: Float; - static var lastTime: Float = 0; - static var totalTime: Float = 0; - static var frames = 0; - static var frameTimeAvg = 0.0; - - public static function render(g: kha.graphics4.Graphics) { - frameTime = kha.Scheduler.realTime() - lastTime; - lastTime = kha.Scheduler.realTime(); - totalTime += frameTime; - frames++; - if (totalTime >= 1) { - frameTimeAvg = Std.int(totalTime / frames * 10000) / 10; - totalTime = 0; - frames = 0; - } + if (needsResize) { + if (renderSize != null) iron.App.removeRender(renderSize); + renderSize = function(g: kha.graphics4.Graphics) { + for (rt in path.renderTargets) { + if (rt.raw.scale != null) rt.raw.scale = dynamicScale; + } + path.resize(); + }; + iron.App.notifyOnRender(renderSize); + needsResize = false; + } } } diff --git a/leenkx/Sources/leenkx/renderpath/Inc.hx b/leenkx/Sources/leenkx/renderpath/Inc.hx index 02e9433d..fb99c702 100644 --- a/leenkx/Sources/leenkx/renderpath/Inc.hx +++ b/leenkx/Sources/leenkx/renderpath/Inc.hx @@ -11,8 +11,6 @@ class Inc { static var path: RenderPath; public static var superSample = 1.0; - static var pointIndex = 0; - static var spotIndex = 0; static var lastFrame = -1; #if lnx_shadowmap_atlas @@ -437,37 +435,43 @@ class Inc { path.bindTarget(n, n); #end } - for (i in 0...pointIndex) { - var n = "shadowMapPoint[" + i + "]"; - path.bindTarget(n, n); - #if rp_shadowmap_transparent - var n = "shadowMapPointTransparent[" + i + "]"; - path.bindTarget(n, n); - #end - } - for (i in 0...spotIndex) { - var n = "shadowMapSpot[" + i + "]"; - path.bindTarget(n, n); - #if rp_shadowmap_transparent - var n = "shadowMapSpotTransparent[" + i + "]"; - path.bindTarget(n, n); - #end + var lightIndex = 0; + for (l in iron.Scene.active.lights) { + if (iron.object.LightObject.discardLightCulled(l)) continue; + + if (l.data.raw.type == "point") { + var n = "shadowMapPoint[" + lightIndex + "]"; + path.bindTarget(n, n); + #if rp_shadowmap_transparent + var n = "shadowMapPointTransparent[" + lightIndex + "]"; + path.bindTarget(n, n); + #end + } + else if (l.data.raw.type == "spot" || l.data.raw.type == "area") { + var n = "shadowMapSpot[" + lightIndex + "]"; + path.bindTarget(n, n); + #if rp_shadowmap_transparent + var n = "shadowMapSpotTransparent[" + lightIndex + "]"; + path.bindTarget(n, n); + #end + } + lightIndex++; } } - static function shadowMapName(light: LightObject, transparent: Bool): String { + static function shadowMapName(light: LightObject, transparent: Bool, index: Int): String { switch (light.data.raw.type) { case "sun": return transparent ? "shadowMapTransparent" : "shadowMap"; case "point": - return transparent ? "shadowMapPointTransparent[" + pointIndex + "]" : "shadowMapPoint[" + pointIndex + "]"; + return transparent ? "shadowMapPointTransparent[" + index + "]" : "shadowMapPoint[" + index + "]"; default: - return transparent ? "shadowMapSpotTransparent[" + spotIndex + "]" : "shadowMapSpot[" + spotIndex + "]"; + return transparent ? "shadowMapSpotTransparent[" + index + "]" : "shadowMapSpot[" + index + "]"; } } - static function getShadowMap(l: iron.object.LightObject, transparent: Bool): String { - var target = shadowMapName(l, transparent); + static function getShadowMap(l: iron.object.LightObject, transparent: Bool, index: Int): String { + var target = shadowMapName(l, transparent, index); var rt = path.renderTargets.get(target); // Create shadowmap on the fly if (rt == null) { @@ -510,14 +514,13 @@ class Inc { lastFrame = RenderPath.active.frame; #end - pointIndex = 0; - spotIndex = 0; + var lightIndex = 0; for (l in iron.Scene.active.lights) { if (!l.visible) continue; if (l.data.raw.type == "sun" && l != RenderPath.active.sun) continue; path.light = l; - var shadowmap = Inc.getShadowMap(l, false); + var shadowmap = Inc.getShadowMap(l, false, lightIndex); var faces = l.data.raw.shadowmap_cube ? 6 : 1; for (i in 0...faces) { if (faces > 1) path.currentFace = i; @@ -529,19 +532,19 @@ class Inc { } path.currentFace = -1; - if (l.data.raw.type == "point") pointIndex++; - else if (l.data.raw.type == "spot" || l.data.raw.type == "area") spotIndex++; + if (!iron.object.LightObject.discardLightCulled(l)) { + lightIndex++; + } } #if rp_shadowmap_transparent - pointIndex = 0; - spotIndex = 0; + lightIndex = 0; for (l in iron.Scene.active.lights) { if (!l.visible) continue; if (l.data.raw.type == "sun" && l != RenderPath.active.sun) continue; path.light = l; - var shadowmap_transparent = Inc.getShadowMap(l, true); + var shadowmap_transparent = Inc.getShadowMap(l, true, lightIndex); var faces = l.data.raw.shadowmap_cube ? 6 : 1; for (i in 0...faces) { if (faces > 1) path.currentFace = i; @@ -553,8 +556,9 @@ class Inc { } path.currentFace = -1; - if (l.data.raw.type == "point") pointIndex++; - else if (l.data.raw.type == "spot" || l.data.raw.type == "area") spotIndex++; + if (!iron.object.LightObject.discardLightCulled(l)) { + lightIndex++; + } } #end #end // rp_shadowmap diff --git a/leenkx/Sources/leenkx/renderpath/Postprocess.hx b/leenkx/Sources/leenkx/renderpath/Postprocess.hx index cfa1e7f6..83fe4e91 100644 --- a/leenkx/Sources/leenkx/renderpath/Postprocess.hx +++ b/leenkx/Sources/leenkx/renderpath/Postprocess.hx @@ -138,6 +138,12 @@ class Postprocess { 0 //1: Filter ]; + public static var auto_focus = [ + 0.5, // auto focus x + 0.5, // auto focus y + 1.0 // maxblur + ]; + public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { var v:Vec4 = null; @@ -395,6 +401,11 @@ class Postprocess { v.y = volumetric_fog_uniforms[0][1]; v.z = volumetric_fog_uniforms[0][2]; v.w = volumetric_fog_uniforms[1][0]; //Amount A + case "_PPComp19": + v = iron.object.Uniforms.helpVec; + v.x = auto_focus[0]; + v.y = auto_focus[1]; + v.z = auto_focus[2]; } return v; diff --git a/leenkx/Sources/leenkx/renderpath/RenderPathForward.hx b/leenkx/Sources/leenkx/renderpath/RenderPathForward.hx index bcddc2b0..ca9b596d 100644 --- a/leenkx/Sources/leenkx/renderpath/RenderPathForward.hx +++ b/leenkx/Sources/leenkx/renderpath/RenderPathForward.hx @@ -531,6 +531,14 @@ class RenderPathForward { } #end + #if rp_dynres + { + if (leenkx.data.Config.raw.rp_dynres != false) { + DynamicResolutionScale.run(path); + } + } + #end + #if rp_stereo { path.drawStereo(drawMeshes); diff --git a/leenkx/Sources/leenkx/system/Starter.hx b/leenkx/Sources/leenkx/system/Starter.hx index 9220486d..5e4d294a 100644 --- a/leenkx/Sources/leenkx/system/Starter.hx +++ b/leenkx/Sources/leenkx/system/Starter.hx @@ -9,6 +9,9 @@ class Starter { public static var numAssets: Int; #end + public static var assets: Array; + public static var scenes: Array; + public static function main(scene: String, mode: Int, resize: Bool, min: Bool, max: Bool, w: Int, h: Int, msaa: Int, vsync: Bool, getRenderPath: Void->iron.RenderPath) { var tasks = 0; diff --git a/leenkx/Sources/leenkx/trait/Character.hx b/leenkx/Sources/leenkx/trait/Character.hx index c99a0f64..5bd0cca9 100644 --- a/leenkx/Sources/leenkx/trait/Character.hx +++ b/leenkx/Sources/leenkx/trait/Character.hx @@ -17,10 +17,10 @@ class Character extends Trait { var lastLoc: Vec4 = null; var framesIdle = 0; // Number of frames character did not move - @:prop + @prop var actionIdle: String = "idle"; - @:prop + @prop var actionMove: String = "run"; public function new() { diff --git a/leenkx/Sources/leenkx/trait/CustomParticle.hx b/leenkx/Sources/leenkx/trait/CustomParticle.hx index d7fce492..ad35abb0 100644 --- a/leenkx/Sources/leenkx/trait/CustomParticle.hx +++ b/leenkx/Sources/leenkx/trait/CustomParticle.hx @@ -27,14 +27,4 @@ class CustomParticle extends iron.Trait { meshObjGeom.instanceCount = count; } } - - public function updateParticleCount(count: Int){ - - if(object.raw.type == 'mesh_object') - { - var meshObjGeom = cast(object, MeshObject).data.geom; - meshObjGeom.instanced = true; - meshObjGeom.instanceCount = count; - } - } } diff --git a/leenkx/Sources/leenkx/trait/FirstPersonController.hx b/leenkx/Sources/leenkx/trait/FirstPersonController.hx index 51f5186c..48486f98 100644 --- a/leenkx/Sources/leenkx/trait/FirstPersonController.hx +++ b/leenkx/Sources/leenkx/trait/FirstPersonController.hx @@ -42,12 +42,10 @@ class FirstPersonController extends Trait { @prop public var staRecoverTime:Float = 2.0; @prop public var staDecreasePerJump:Float = 5.0; @prop public var enableFatigue:Bool = false; - @prop public var fatigueSpeed:Float = 0.5; // the reduction of movement when fatigue is activated... - @prop public var fatigueThreshold:Float = 30.0; // Tiempo corriendo sin parar para la activacion // Time running non-stop for activation... - @prop public var fatRecoveryThreshold:Float = 7.5; // Tiempo sin correr/saltar para salir de fatiga // Time without running/jumping to get rid of fatigue... + @prop public var fatigueSpeed:Float = 0.5; + @prop public var fatigueThreshold:Float = 30.0; + @prop public var fatRecoveryThreshold:Float = 7.5; - - // Var Privadas var head:CameraObject; var pitch:Float = 0.0; var body:RigidBody; @@ -136,15 +134,11 @@ class FirstPersonController extends Trait { } #end - // Dejo establecido el salto para tener en cuenta (isFatigued) - // I set the jump to take into account the (isFatigued) if (isGrounded && !isFatigued()) { canJump = true; } - // Saltar con estamina // Jump with stamina if (enableJump && kb.started(jumpKey) && canJump) { var jumpPower = jumpForce; - // Disminuir el salto al 50% si la (stamina) esta por debajo o en el 20%. // Decrease jump to 50% if stamina is below or at 20% if (stamina) { if (staminaValue <= 0) { jumpPower = 0; @@ -163,7 +157,6 @@ class FirstPersonController extends Trait { } } - // Control de estamina y correr // Control of stamina and running if (canRun && kb.down(runKey) && isMoving) { if (stamina) { if (staminaValue > 0.0) { @@ -190,25 +183,20 @@ class FirstPersonController extends Trait { fatigueCooldown += deltaTime; } - // Evitar correr y saltar al estar fatigado // Avoid running and jumping when fatigued if (isFatigued()) { isRunning = false; canJump = false; } - // Activar fatiga despues de correr continuamente durante cierto umbral - // Activate fatigue after running continuously for a certain threshold if (enableFatigue && fatigueTimer >= fatigueThreshold) { isFatigueActive = true; } - // Eliminar la fatiga despues de recuperarse // Eliminate fatigue after recovery (fatRecoveryThreshold) if (enableFatigue && isFatigueActive && fatigueCooldown >= fatRecoveryThreshold) { isFatigueActive = false; fatigueTimer = 0.0; } - // Recuperar estamina si no esta corriendo // Recover stamina if you re not running () if (stamina && !isRunning && staminaValue < staminaBase && !isFatigued()) { if (timeSinceStop >= staRecoverTime) { staminaValue += staRecoverPerSec * deltaTime; @@ -216,7 +204,6 @@ class FirstPersonController extends Trait { } } - // Movimiento en ejes locales // Movement on local axies dir.set(0, 0, 0); if (moveForward) dir.add(object.transform.look()); if (moveBackward) dir.add(object.transform.look().mult(-1)); @@ -244,7 +231,4 @@ class FirstPersonController extends Trait { } #end -} - - -// Stamina and fatigue system..... \ No newline at end of file +} \ No newline at end of file diff --git a/leenkx/Sources/leenkx/trait/FollowCurve.hx b/leenkx/Sources/leenkx/trait/FollowCurve.hx new file mode 100644 index 00000000..361ac2b8 --- /dev/null +++ b/leenkx/Sources/leenkx/trait/FollowCurve.hx @@ -0,0 +1,64 @@ +package leenkx.trait; + +import iron.object.CurveObject; +import iron.Scene; +import iron.system.Time; + +class FollowCurve extends iron.Trait { + + var curve: CurveObject = null; + var progress: Float = 0.0; + + @prop + var curveName: String = ""; + + @prop + var splineIndex: Int = 0; + + @prop + var forwardAxis: String = 'X'; + + @prop + var speed: Float = 1; + + @prop + var equidistantSamples: Int = 0; + + @prop + var forward: Bool = true; + + @prop + var cyclic: Bool = true; + + @prop + var start: Float = 0.0; + + public function new() { + super(); + + notifyOnInit(function() { + var obj = Scene.active.getChild(curveName); + if (obj != null && Std.isOfType(obj, CurveObject)) { + curve = cast obj; + curve.equidistantSamples = equidistantSamples; + progress = start; + notifyOnUpdate(update); + } + }); + } + + function update() { + var len = curve.getLength(splineIndex); + var currentDist = progress * len; + currentDist += (speed * Time.delta * (forward ? 1.0 : -1.0)); + + if (cyclic){ + if (currentDist > len) currentDist -= len; + else if (currentDist < 0) currentDist += len; + } + + progress = (len > 0) ? currentDist / len : 0.0; + + curve.follow(object, progress, splineIndex, forwardAxis); + }; +} diff --git a/leenkx/Sources/leenkx/trait/KinematicController.hx b/leenkx/Sources/leenkx/trait/KinematicController.hx new file mode 100644 index 00000000..751a0f6d --- /dev/null +++ b/leenkx/Sources/leenkx/trait/KinematicController.hx @@ -0,0 +1,89 @@ +package leenkx.trait; + +import iron.Trait; +import leenkx.trait.physics.bullet.KinematicCharacterController; +import leenkx.trait.physics.bullet.KinematicCharacterController.ControllerShape; +import iron.system.Input; +import iron.system.Input.Keyboard; +import iron.math.Vec4; + +class KinematicController extends Trait { + + @prop + public var mass:Float = 75.0; + + @prop + public var speed:Float = 0.08; + + @prop + public var jumpSpeed:Float = 10.0; + + @prop + public var fallSpeed:Float = 55.0; + + @prop + public var maxSlope:Float = 0.785398; + + @prop + public var maxJumpHeight:Float = 5.0; + + @prop + public var friction:Float = 0.8; + + var controller:KinematicCharacterController; + var keyboard:Keyboard = Input.getKeyboard(); + var initComplete:Bool = false; + var jumpV:Vec4 = new Vec4(0.0, 0.0, 8.0); + var move:Vec4 = new Vec4(); + + public function new() { + super(); + iron.Scene.active.notifyOnInit(init); + notifyOnUpdate(update); + } + + function init() { + controller = new KinematicCharacterController(mass, ControllerShape.Capsule); + controller.notifyOnReady(onControllerReady); + object.addTrait(controller); + } + + function onControllerReady() { + controller.setJumpSpeed(jumpSpeed); + controller.setFallSpeed(fallSpeed); + controller.setMaxSlope(maxSlope); + controller.setMaxJumpHeight(maxJumpHeight); + controller.setUpInterpolate(true); + controller.setFriction(friction); + + initComplete = true; + } + + function update() { + if (!initComplete) return; + + move.set(0, 0, 0); + if (keyboard.down("up") || keyboard.down("w")) move.y += 1; + if (keyboard.down("down") || keyboard.down("s")) move.y -= 1; + if (keyboard.down("left") || keyboard.down("a")) move.x -= 1; + if (keyboard.down("right") || keyboard.down("d")) move.x += 1; + + if (move.length() > 0) { + move.normalize(); + move.mult(speed); + } + + if (keyboard.started("space")) { + if (controller.canJump() && controller.onGround()) { + #if js + controller.jump(); + #elseif cpp + jumpV.z = jumpSpeed; + controller.jump(jumpV); + #end + } + } + + controller.setWalkDirection(move); + } +} diff --git a/leenkx/Sources/leenkx/trait/MeshBuild.hx b/leenkx/Sources/leenkx/trait/MeshBuild.hx new file mode 100644 index 00000000..00e274ad --- /dev/null +++ b/leenkx/Sources/leenkx/trait/MeshBuild.hx @@ -0,0 +1,131 @@ +package leenkx.trait; + +import iron.Trait; +import iron.object.MeshObject; +import iron.system.Time; + +class MeshBuild extends Trait { + + @prop + public var startFrame: Float = 1.0; + + @prop + public var length: Float = 100.0; + + @prop + public var reversed: Bool = false; + + @prop + public var randomize: Bool = false; + + var mesh: MeshObject; + var totalTris: Int = 0; + var totalFaces: Int = 0; + var totalIndices: Int = 0; + var elapsedFrames: Float = 0.0; + var isRunning: Bool = true; + + var origIndices: Array = []; + var faceOrder: Array = []; + + public function new() { + super(); + notifyOnAdd(init); + } + + function init() { + mesh = cast(object, MeshObject); + if (mesh == null || mesh.data == null || mesh.data.geom == null) return; + + totalTris = mesh.data.geom.numTris; + totalIndices = totalTris * 3; + totalFaces = Std.int(Math.ceil(totalTris / 2)); + + if (mesh.data.geom.indexBuffers.length > 0) { + var ib = mesh.data.geom.indexBuffers[0]; + var locked = ib.lock(); + + for (i in 0...totalIndices) { + origIndices.push(locked[i]); + } + + for (i in 0...totalFaces) { + faceOrder.push(i); + } + + if (randomize) { + for (i in 0...totalFaces) { + var r = Std.int(Math.random() * totalFaces); + var temp = faceOrder[i]; + faceOrder[i] = faceOrder[r]; + faceOrder[r] = temp; + } + } + + ib.unlock(); + } + + mesh.data.geom.count = reversed ? totalIndices : 0; + notifyOnUpdate(update); + } + + function update() { + if (!isRunning) return; + + elapsedFrames += Time.delta * 60.0; + + if (elapsedFrames < startFrame) return; + + var rawProgress = (elapsedFrames - startFrame) / length; + var finished = false; + + if (rawProgress >= 1.0) { + rawProgress = 1.0; + finished = true; + isRunning = false; + } + + var progress = reversed ? (1.0 - rawProgress) : rawProgress; + var targetFaces = Std.int(progress * totalFaces); + + if (mesh.data.geom.indexBuffers.length > 0) { + var ib = mesh.data.geom.indexBuffers[0]; + var indicesA = ib.lock(); + + var writeIdx = 0; + for (f in 0...targetFaces) { + var faceIdx = faceOrder[f]; + var startTri = faceIdx * 2; + + if (startTri * 3 < totalIndices) { + indicesA[writeIdx] = origIndices[startTri * 3]; + indicesA[writeIdx + 1] = origIndices[startTri * 3 + 1]; + indicesA[writeIdx + 2] = origIndices[startTri * 3 + 2]; + writeIdx += 3; + } + + if ((startTri + 1) * 3 < totalIndices) { + indicesA[writeIdx] = origIndices[(startTri + 1) * 3]; + indicesA[writeIdx + 1] = origIndices[(startTri + 1) * 3 + 1]; + indicesA[writeIdx + 2] = origIndices[(startTri + 1) * 3 + 2]; + writeIdx += 3; + } + } + + ib.unlock(); + mesh.data.geom.count = writeIdx; + } + + if (finished && !reversed) { + if (mesh.data.geom.indexBuffers.length > 0) { + var ib = mesh.data.geom.indexBuffers[0]; + var indicesA = ib.lock(); + for (i in 0...totalIndices) { + indicesA[i] = origIndices[i]; + } + ib.unlock(); + mesh.data.geom.count = totalIndices; + } + } + } +} diff --git a/leenkx/Sources/leenkx/trait/MeshGenerator.hx b/leenkx/Sources/leenkx/trait/MeshGenerator.hx new file mode 100644 index 00000000..9751cfcd --- /dev/null +++ b/leenkx/Sources/leenkx/trait/MeshGenerator.hx @@ -0,0 +1,173 @@ +package leenkx.trait; + +import iron.Trait; +import iron.math.Vec4; +import iron.object.Object; +import iron.object.MeshObject; +import iron.object.Transform; +import iron.Scene; +import iron.data.SceneFormat; +import iron.data.Data; +import iron.data.MeshData; +import iron.data.MaterialData; + +import leenkx.object.MeshDataExtension; + +class MeshGenerator extends Trait { + + @prop + var numPoints: Int = 5; + + @prop + var radius: Float = 5.0; + + @prop + var mirrorX: Bool = false; + + @prop + var mirrorY: Bool = false; + + @prop + var mirrorZ: Bool = false; + + @prop + var material: String = ''; + + @prop + var scaleUV: Float = 0.3; + + @prop + var loc: Vec4 = new Vec4(0, 0, 0); + + @prop + var rot: Vec4 = new Vec4(0, 0, 0); + + @prop + var traitName: String = ''; + + public function new() { + super(); + notifyOnInit(init); + } + + function init() { + + if (numPoints < 4) numPoints = 4; + if (radius < 0.1) radius = 0.1; + + var md: MeshData = null; + + while (md == null){ + var points: Array = []; + + for (i in 0...numPoints) { + var x = (Math.random() - 0.5) * radius; + var y = (Math.random() - 0.5) * radius; + var z = (Math.random() - 0.5) * radius; + + points.push(new Vec4(x, y, z)); + + if (mirrorX) + points.push(new Vec4(-x, y, z)); + + if (mirrorY) + points.push(new Vec4(x, -y, z)); + + if (mirrorZ) + points.push(new Vec4(x, y, -z)); + + if (mirrorX && mirrorY) + points.push(new Vec4(-x, -y, z)); + + if (mirrorX && mirrorZ) + points.push(new Vec4(-x, y, -z)); + + if (mirrorY && mirrorZ) + points.push(new Vec4(x, -y, -z)); + + if (mirrorX && mirrorY && mirrorZ) + points.push(new Vec4(-x, -y, -z)); + } + + md = MeshDataExtension.makeMeshData(points, scaleUV); + + } + + /*safe mesh + // Plane + points = [ + new Vec4(-1.0, 0.0, -1.0), // + new Vec4( 1.0, 0.0, -1.0), // + new Vec4( 1.0, 0.0, 1.0), // + new Vec4(-1.0, 0.0, 1.0) // + ]; + + if (md == null) { + points = [ + new Vec4(-0.5, -0.5, -0.5), new Vec4(0.5, -0.5, -0.5), + new Vec4(0.5, 0.5, -0.5), new Vec4(-0.5, 0.5, -0.5), + new Vec4(-0.5, -0.5, 0.5), new Vec4(0.5, -0.5, 0.5), + new Vec4(0.5, 0.5, 0.5), new Vec4(-0.5, 0.5, 0.5) + ]; + + for (p in points) { + p.x *= radius; + p.y *= radius; + p.z *= radius; + + var offset = radius * 0.1; + p.x += (Math.random() - 0.5) * offset; + p.y += (Math.random() - 0.5) * offset; + p.z += (Math.random() - 0.5) * offset; + } + + md = MeshDataExtension.makeMeshData(points); + } + */ + + var mat = new haxe.ds.Vector(1); + + if (material != '') + Data.getMaterial(Scene.active.raw.name, material, function(data:MaterialData) { + mat[0] = data != null ? data : Scene.active.meshes[0].materials[0]; + }); + else + mat[0] = Scene.active.meshes[0].materials[0]; + + var obj: Object = cast new MeshObject(md, mat); + obj.name = md.name; + + var s = md.scalePos; + var v = new kha.arrays.Float32Array(16); + v[0] = s; v[5] = s; v[10] = s; v[15] = 1.0; + + var dims = new kha.arrays.Float32Array(3); + dims[0] = md.geom.aabb.x; + dims[1] = md.geom.aabb.y; + dims[2] = md.geom.aabb.z; + + obj.raw = { + type: "mesh_object", + name: md.name, + data_ref: md.name, + transform: { values: v }, + dimensions: dims + }; + + obj.setParent(Scene.active.root); + obj.transform.loc.setFrom(loc); + var degToRad = Math.PI / 180; + obj.transform.setRotation(rot.x * degToRad, rot.y * degToRad, rot.z * degToRad); + obj.transform.buildMatrix(); + //obj.addTrait(new leenkx.trait.physics.RigidBody()); + obj.addTrait(new leenkx.trait.internal.UniformsManager()); + if (traitName != ''){ + var cname = Type.resolveClass(Main.projectPackage + "." + traitName); + if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName); + var trait = Type.createInstance(cname, []); + obj.addTrait(trait); + } + + } + +} diff --git a/leenkx/Sources/leenkx/trait/MeshRenderDraw.hx b/leenkx/Sources/leenkx/trait/MeshRenderDraw.hx new file mode 100644 index 00000000..c2d83632 --- /dev/null +++ b/leenkx/Sources/leenkx/trait/MeshRenderDraw.hx @@ -0,0 +1,52 @@ +package leenkx.trait; + +import leenkx.trait.internal.RenderDraw; +import iron.math.Vec4; + +class MeshRenderDraw extends iron.Trait { + + @prop + public var strength:Float = 0.1; + + @prop + public var color:Vec4 = new Vec4(0, 0, 0, 1); + + @prop + public var showBounds:Bool = true; + + @prop + public var showWireframe:Bool = false; + + @prop + public var showOutline:Bool = false; + + @prop + public var active:Bool = true; + + var renderCb: kha.graphics4.Graphics->Int->Int->Void = null; + + public function new() { + super(); + notifyOnInit(function() { + renderCb = RenderDraw.notifyOnRender( (draw:RenderDraw) -> { + if (!active) return; + draw.strength = strength; + draw.color = kha.Color.fromFloats(color.x, color.y, color.z, color.w); + + if (showBounds) draw.bounds(object.transform); + if (showOutline) draw.outline(cast object); + if (showWireframe) draw.wireframe(cast object); + + }); + }); + } + + override public function remove() { + active = false; + if (renderCb != null) { + RenderDraw.removeOnRender(renderCb); + renderCb = null; + } + super.remove(); + } +} diff --git a/leenkx/Sources/leenkx/trait/NavAgent.hx b/leenkx/Sources/leenkx/trait/NavAgent.hx index 5eb7246c..247adceb 100644 --- a/leenkx/Sources/leenkx/trait/NavAgent.hx +++ b/leenkx/Sources/leenkx/trait/NavAgent.hx @@ -3,79 +3,160 @@ package leenkx.trait; import iron.Trait; import iron.math.Vec4; import iron.math.Quat; -import iron.system.Tween; +import iron.system.Time; +#if lnx_navigation +import leenkx.trait.navigation.Navigation; +#end class NavAgent extends Trait { @prop + public var navMeshId: String = 'NavMesh'; + + @prop public var speed: Float = 5; + @prop public var turnDuration: Float = 0.4; + @prop public var heightOffset: Float = 0.0; + @prop + public var pathCheckTolerance: Float = 0.5; + var path: Array = null; var index = 0; - - var rotAnim: TAnim = null; - var locAnim: TAnim = null; - - public var tickPos: Null Void>; - public var tickRot: Null Void>; + var isUpdating: Bool = false; + var activeNavMesh: NavMesh = null; public function new() { super(); - notifyOnRemove(stopTween); + notifyOnUpdate(initNavAgent); + } + + function initNavAgent() { + #if lnx_navigation + activeNavMesh = Navigation.active.navMeshes.get(navMeshId); + if(activeNavMesh == null || !activeNavMesh.ready) return; + removeUpdate(initNavAgent); + #end } public function setPath(path: Array) { - stopTween(); - this.path = path; index = 1; - notifyOnUpdate(update); - - go(); - } - - function stopTween() { - if (rotAnim != null) Tween.stop(rotAnim); - if (locAnim != null) Tween.stop(locAnim); + if (!isUpdating) { + notifyOnUpdate(update); + isUpdating = true; + } } public function stop() { - stopTween(); + if (isUpdating) { + removeUpdate(update); + isUpdating = false; + } path = null; } - function shortAngle(from: Float, to: Float): Float { - if (from < 0) from += Math.PI * 2; - if (to < 0) to += Math.PI * 2; - var delta = Math.abs(from - to); - if (delta > Math.PI) to = Math.PI * 2 - delta; - return to; - } - - var orient = new Vec4(); - function go() { - if (path == null || index >= path.length) return; - - var p = path[index]; - var dist = Vec4.distance(object.transform.loc, p); - - orient.subvecs(p, object.transform.loc).normalize; - var targetAngle = Math.atan2(orient.y, orient.x) + Math.PI / 2; - locAnim = Tween.to({ target: object.transform.loc, props: { x: p.x, y: p.y, z: p.z + heightOffset }, duration: dist / speed, tick: tickPos, done: function() { - index++; - if (index < path.length) go(); - else removeUpdate(update); - }}); - - var q = new Quat(); - rotAnim = Tween.to({ target: object.transform, props: { rot: q.fromEuler(0, 0, targetAngle) }, tick: tickRot, duration: turnDuration}); - } - function update() { + if (activeNavMesh == null || path == null || index >= path.length) { + stop(); + return; + } + + var currentLoc = object.transform.loc; + var p = path[index]; + + var dir = new Vec4().subvecs(p, currentLoc); + + var normal = activeNavMesh.getNavMeshNormal(currentLoc); + normal.normalize(); + + var normalDot = dir.dot(normal); + + dir.x -= normal.x * normalDot; + dir.y -= normal.y * normalDot; + dir.z -= normal.z * normalDot; + + var dist = dir.length(); + + if (dist < pathCheckTolerance) { + index++; + + if (index >= path.length) { + stop(); + return; + } + + p = path[index]; + + dir.subvecs(p, currentLoc); + + normal = activeNavMesh.getNavMeshNormal(currentLoc); + normal.normalize(); + + normalDot = dir.dot(normal); + + dir.x -= normal.x * normalDot; + dir.y -= normal.y * normalDot; + dir.z -= normal.z * normalDot; + + dist = dir.length(); + } + + dir.normalize(); + + var step = speed * Time.delta; + if (step > dist) step = dist; + + var nextPos = new Vec4( + currentLoc.x + dir.x * step, + currentLoc.y + dir.y * step, + currentLoc.z + dir.z * step + ); + + normal = activeNavMesh.getNavMeshNormal(nextPos); + normal.normalize(); + + object.transform.loc.set( + nextPos.x, + nextPos.y, + nextPos.z + ); + + var targetRot = new Quat().fromTo(new Vec4(0, 0, 1, 0), normal); + + var forward = new Vec4().setFrom(dir); + + var ndot = forward.dot(normal); + + forward.x -= normal.x * ndot; + forward.y -= normal.y * ndot; + forward.z -= normal.z * ndot; + + if (forward.length() > 0.0001) { + forward.normalize(); + + var xAxis = new Vec4(1, 0, 0, 0); + xAxis.applyQuat(targetRot); + + var currForward = new Vec4().setFrom(xAxis); + + var twist = new Quat().fromTo(currForward, forward); + + targetRot = twist.mult(targetRot); + } + + var currentRot = new Quat().setFrom(object.transform.rot); + + var t = turnDuration > 0 ? Time.delta / turnDuration : 1.0; + + if (t > 1.0) t = 1.0; + + object.transform.rot = new Quat().lerp(currentRot, targetRot, t); + object.transform.buildMatrix(); } } diff --git a/leenkx/Sources/leenkx/trait/NavBoxObstacle.hx b/leenkx/Sources/leenkx/trait/NavBoxObstacle.hx new file mode 100644 index 00000000..1781d75a --- /dev/null +++ b/leenkx/Sources/leenkx/trait/NavBoxObstacle.hx @@ -0,0 +1,65 @@ +package leenkx.trait; + +#if lnx_navigation +import iron.math.Vec4; +import leenkx.trait.navigation.Navigation; +#end +import iron.Trait; + +class NavBoxObstacle extends Trait { + + #if !lnx_navigation + public function new() { super(); } + #else + + @prop + public var navMeshId: String = 'NavMesh'; + + @prop + public var dimensions: Vec4 = new Vec4(1.0, 1.0, 1.0); + + @prop + public var angle: Float = 0; + + @prop + public var isStatic: Bool = true; + + var obstacleID: Int = -1; + + public var obstacleReady (default, null) = false; + + var activeNavMesh: NavMesh = null; + + var initialPosition: Vec4 = new Vec4(); + + public function new() { + super(); + notifyOnUpdate(addObstacle); + notifyOnRemove(removeObstacle); + } + + function addObstacle() { + activeNavMesh = Navigation.active.navMeshes.get(navMeshId); + if(activeNavMesh == null || !activeNavMesh.ready) return; + + initialPosition = object.transform.world.getLoc(); + obstacleID = activeNavMesh.addBoxObstacle(this, initialPosition, dimensions, -angle); + + if (!isStatic) + notifyOnUpdate(updateObstaclePosition); + removeUpdate(addObstacle); + obstacleReady = true; + } + + function removeObstacle() { + activeNavMesh.removeTempObstacle(obstacleID); + } + + function updateObstaclePosition() { + if (!object.transform.world.getLoc().almostEquals(initialPosition, 0.01)) + activeNavMesh.updateObstaclePosition(obstacleID); + //object.transform.loc.setFrom(initialPosition); + //object.transform.buildMatrix(); + } + #end +} diff --git a/leenkx/Sources/leenkx/trait/NavCrowd.hx b/leenkx/Sources/leenkx/trait/NavCrowd.hx index 40e7a32a..e3727ab7 100644 --- a/leenkx/Sources/leenkx/trait/NavCrowd.hx +++ b/leenkx/Sources/leenkx/trait/NavCrowd.hx @@ -13,6 +13,9 @@ class NavCrowd extends Trait { public function new() { super(); } #else + @prop + public var navMeshId: String = 'NavMesh'; + // Position offset for the agent. @prop public var offset: Vec4 = new Vec4(); @@ -35,7 +38,7 @@ class NavCrowd extends Trait { @prop public var turnSpeed: Float = 0.1; - // Threshold to avoid turning at low velocities which might causer jittering. + // Threshold to avoid turning at low velocities which might cause jittering. @prop public var turnVelocityThreshold: Float = 0.1; @@ -75,8 +78,6 @@ class NavCrowd extends Trait { var activeNavMesh: NavMesh = null; var agentID = -1; - static inline var EPSILON = 0.0001; - public function new() { super(); @@ -86,11 +87,8 @@ class NavCrowd extends Trait { function addAgent() { - if(Navigation.active.navMeshes.length < 1) return; - - if(! Navigation.active.navMeshes[0].ready) return; - - activeNavMesh = Navigation.active.navMeshes[0]; + activeNavMesh = Navigation.active.navMeshes.get(navMeshId); + if(activeNavMesh == null || !activeNavMesh.ready) return; var flags: Int = 0; if(anticipateTurns) flags += 1; @@ -107,6 +105,52 @@ class NavCrowd extends Trait { agentReady = true; } + public function crowdAgentMaxSpeed():Float { + if (!agentReady) return 0.0; + return activeNavMesh.crowdGetAgentMaxSpeed(agentID); + } + + public function crowdAgentMaxAcceleration():Float { + if (!agentReady) return 0.0; + return activeNavMesh.crowdGetAgentMaxAcceleration(agentID); + } + + public function crowdAgentSetMaxSpeed(speed:Float):Void { + if (!agentReady) return; + maxSpeed = speed; + activeNavMesh.crowdSetAgentMaxSpeed(agentID, speed); + } + + public function crowdAgentSetMaxAcceleration(acceleration:Float):Void { + if (!agentReady) return; + maxAcceleration = acceleration; + activeNavMesh.crowdSetAgentMaxAcceleration(agentID, acceleration); + } + + public function crowdAgentVelocity(): Vec4 { + if(!agentReady) return null; + + return activeNavMesh.crowdGetAgentVelocity(agentID); + } + + public function crowdAgentPosition(): Vec4 { + if(!agentReady) return null; + + return activeNavMesh.crowdGetAgentPosition(agentID); + } + + public function crowdAgentNextPath(): Vec4 { + if(!agentReady) return null; + + return activeNavMesh.crowdGetAgentNextTargetPath(agentID); + } + + public function crowdAgentPath(): Array { + if(!agentReady) return null; + + return activeNavMesh.crowdGetCorners(agentID); + } + public function crowdAgentGoto(position: Vec4) { if(!agentReady) return; @@ -116,7 +160,7 @@ class NavCrowd extends Trait { public function crowdAgentTeleport(position: Vec4) { if(!agentReady) return; - activeNavMesh.crowdAgentTeleport(agentID, position); + activeNavMesh.crowdAgentTeleport(agentID, position.sub(offset)); } function removeAgent() { @@ -133,14 +177,25 @@ class NavCrowd extends Trait { } function turnAgent() { + var pos = activeNavMesh.crowdGetAgentPosition(agentID); var vel = activeNavMesh.crowdGetAgentVelocity(agentID); - vel.z = 0; - if(vel.length() < turnVelocityThreshold) return; - vel.normalize(); - var targetRot = new Quat().fromTo(new Vec4(1, 0, 0, 1), vel); + var normal = activeNavMesh.getNavMeshNormal(pos); var currentRot = new Quat().setFrom(object.transform.rot); - var res = new Quat().lerp(currentRot, targetRot, turnSpeed); - object.transform.rot = res; + + var up = new Vec4(0, 0, 1, 0); + up.applyQuat(currentRot); + object.transform.rot = new Quat().fromTo(up, normal).mult(currentRot); + + vel.z = 0; + if (vel.length() < turnVelocityThreshold) return; + vel.normalize(); + + var targetRotBase = new Quat().fromTo(new Vec4(1, 0, 0, 1), vel); + var upVec = new Vec4(0, 0, 1, 0); + upVec.applyQuat(targetRotBase); + var targetRot = new Quat().fromTo(upVec, normal).mult(targetRotBase); + + object.transform.rot = new Quat().lerp(new Quat().setFrom(object.transform.rot), targetRot, turnSpeed); } #end } \ No newline at end of file diff --git a/leenkx/Sources/leenkx/trait/NavObstacle.hx b/leenkx/Sources/leenkx/trait/NavCylinderObstacle.hx similarity index 56% rename from leenkx/Sources/leenkx/trait/NavObstacle.hx rename to leenkx/Sources/leenkx/trait/NavCylinderObstacle.hx index c98f04dd..bfce4fa5 100644 --- a/leenkx/Sources/leenkx/trait/NavObstacle.hx +++ b/leenkx/Sources/leenkx/trait/NavCylinderObstacle.hx @@ -6,18 +6,27 @@ import leenkx.trait.navigation.Navigation; #end import iron.Trait; -class NavObstacle extends Trait { +class NavCylinderObstacle extends Trait { #if !lnx_navigation public function new() { super(); } #else + @prop + public var navMeshId: String = 'NavMesh'; + + @prop + public var offset: Vec4 = new Vec4(); + @prop public var radius: Float = 1.0; @prop public var height: Float = 1.0; + @prop + public var isStatic: Bool = true; + var obstacleID: Int = -1; public var obstacleReady (default, null) = false; @@ -33,17 +42,14 @@ class NavObstacle extends Trait { } function addObstacle() { + activeNavMesh = Navigation.active.navMeshes.get(navMeshId); + if(activeNavMesh == null || !activeNavMesh.ready) return; - if(Navigation.active.navMeshes.length < 1) return; - - if(! Navigation.active.navMeshes[0].ready) return; - - activeNavMesh = Navigation.active.navMeshes[0]; - - initialPosition = object.transform.world.getLoc(); + initialPosition = object.transform.world.getLoc().sub(offset); obstacleID = activeNavMesh.addCylinderObstacle(this, initialPosition, radius, height); - notifyOnUpdate(updateObstaclePosition); + if (!isStatic) + notifyOnUpdate(updateObstaclePosition); removeUpdate(addObstacle); obstacleReady = true; } @@ -53,8 +59,10 @@ class NavObstacle extends Trait { } function updateObstaclePosition() { - object.transform.loc.setFrom(initialPosition); - object.transform.buildMatrix(); + if (!object.transform.world.getLoc().almostEquals(initialPosition, 0.01)) + activeNavMesh.updateObstaclePosition(obstacleID); + //object.transform.loc.setFrom(initialPosition); + //object.transform.buildMatrix(); } #end -} \ No newline at end of file +} diff --git a/leenkx/Sources/leenkx/trait/NavMesh.hx b/leenkx/Sources/leenkx/trait/NavMesh.hx index 49c2c26a..fe9b3099 100644 --- a/leenkx/Sources/leenkx/trait/NavMesh.hx +++ b/leenkx/Sources/leenkx/trait/NavMesh.hx @@ -24,6 +24,9 @@ class NavMesh extends Trait { public function new() { super(); } #else + @prop + public var navMeshId: String = 'NavMesh'; + @prop public var debugDraw:Bool = false; @@ -124,6 +127,12 @@ class NavMesh extends Trait { @prop public var maxCrowdAgentRadius: Float = 3.0; + @prop + public var defaultAgentQueryExtent: Vec4 = new Vec4(1, 1, 1); + + @prop + public var defaultCrowdQueryExtent: Vec4 = new Vec4(1, 1, 1); + var recastNavMesh: recast.Recast.NavMesh = null; var recastCrowd: recast.Recast.Crowd = null; public var ready(default, null) = false; @@ -145,17 +154,20 @@ class NavMesh extends Trait { notifyOnInit(initNavMesh); notifyOnUpdate(updateNavMesh); + notifyOnRemove(removeNavMesh); } function initNavMesh() { if (ready) return; - Navigation.active.navMeshes.push(this); + Navigation.active.navMeshes.set(navMeshId, this); if(debugDraw) Navigation.active.debugDrawHelper.setDebugMode(DrawWireframe); recastNavMesh = new recast.Recast.NavMesh(); var recastConfig = new recast.Recast.RcConfig(); + recastNavMesh.setDefaultQueryExtent(RecastConversions.recastVec3FromVec4(defaultAgentQueryExtent)); + recastConfig.width = width; recastConfig.height = height; if(tiledMesh) { @@ -226,18 +238,38 @@ class NavMesh extends Trait { } public function reconstructNavMesh() { - removeUpdate(updateNavMesh); - if(recastCrowd != null) { - for(agent in crowdAgentMap.keys()) { - removeCrowdAgent(agent); - } - recastCrowd.destroy(); - } - for(obstacle in tempObstacleMap.keys()) { - removeTempObstacle(obstacle); - } - ready = false; - initNavMesh(); + var savedObstacles = new Map(); + for (key in tempObstacleMap.keys()) { + savedObstacles.set(key, tempObstacleMap.get(key)); + } + + removeUpdate(updateNavMesh); + Navigation.active.navMeshes.remove(navMeshId); + + tempObstacleMap.clear(); + recastObstacleMap.clear(); + + ready = false; + initNavMesh(); + + for (id in savedObstacles.keys()) { + var navObstacle = savedObstacles.get(id); + var newObstacleRef: recast.Recast.DtObstacleRef = null; + + if (Std.is(navObstacle, NavCylinderObstacle)) { + var cylinder = cast(navObstacle, NavCylinderObstacle); + var pos = RecastConversions.recastVec3FromVec4(cylinder.object.transform.world.getLoc()); + newObstacleRef = recastNavMesh.addCylinderObstacle(pos, cylinder.radius, cylinder.height); + } else { + var box = cast(navObstacle, NavBoxObstacle); + var pos = RecastConversions.recastVec3FromVec4(box.object.transform.world.getLoc()); + var dim = RecastConversions.recastVec3FromVec4(box.dimensions); + newObstacleRef = recastNavMesh.addBoxObstacle(pos, dim, box.angle); + } + + tempObstacleMap.set(id, navObstacle); + recastObstacleMap.set(id, newObstacleRef); + } } public function updateNavMesh() { @@ -259,6 +291,12 @@ class NavMesh extends Trait { done(pathVec); } + public function getClosestPoint(position: Vec4): Vec4 { + if (!ready) return null; + var closestPoint = recastNavMesh.getClosestPoint(RecastConversions.recastVec3FromVec4(position)); + return RecastConversions.vec4FromRecastVec3(closestPoint); + } + public function getRandomPointAround(position: Vec4, radius: Float):Vec4 { if (!ready) return null; var randomPoint = recastNavMesh.getRandomPointAround(RecastConversions.recastVec3FromVec4(position), radius); @@ -268,7 +306,11 @@ class NavMesh extends Trait { public function initCrowd(maxAgents: Int, maxAgentRadius: Float) { if (!ready) return; recastCrowd = new recast.Recast.Crowd(maxAgents, maxAgentRadius, recastNavMesh.getNavMesh()); + recastCrowd.setDefaultQueryExtent(RecastConversions.recastVec3FromVec4(defaultCrowdQueryExtent)); notifyOnUpdate(crowdUpdate); + + //recastCrowd.setDefaultQueryExtent(RecastConversions.recastVec3FromVec4(new Vec4(3, 3, 1))); + //trace(RecastConversions.vec4FromRecastVec3(recastCrowd.getDefaultQueryExtent())); } public function addCrowdAgent(agent: NavCrowd, position: Vec4, radius: Float, height: Float, maxAcceleration: Float, maxSpeed: Float, updateFlags: Int = 7, separationWeight: Float = 1.0, collisionQueryRange: Float = 1.0): Int { @@ -301,6 +343,30 @@ class NavMesh extends Trait { recastCrowd.update(Time.delta); } + public function crowdGetAgentMaxSpeed(agentID:Int):Float { + if (!ready || recastCrowd == null) return 0.0; + return recastCrowd.getAgentParameters(agentID).maxSpeed; + } + + public function crowdGetAgentMaxAcceleration(agentID:Int):Float { + if (!ready || recastCrowd == null) return 0.0; + return recastCrowd.getAgentParameters(agentID).maxAcceleration; + } + + public function crowdSetAgentMaxSpeed(agentID:Int, speed:Float):Void { + if (!ready || recastCrowd == null) return; + var params = recastCrowd.getAgentParameters(agentID); + params.maxSpeed = speed; + recastCrowd.setAgentParameters(agentID, params); + } + + public function crowdSetAgentMaxAcceleration(agentID:Int, acceleration:Float):Void { + if (!ready || recastCrowd == null) return; + var params = recastCrowd.getAgentParameters(agentID); + params.maxAcceleration = acceleration; + recastCrowd.setAgentParameters(agentID, params); + } + public function crowdGetAgentPosition(agentID: Int):Vec4 { if(!ready) return null; if(recastCrowd == null) return null; @@ -323,6 +389,32 @@ class NavMesh extends Trait { return armVel; } + public function crowdGetAgentState(agentID: Int): Int { + if (!ready) return -1; + if (recastCrowd == null) return -1; + return recastCrowd.getAgentState(agentID); + } + + public function crowdGetCorners(agentID: Int): Array { + if (!ready) return null; + if (recastCrowd == null) return null; + + var navPath = recastCrowd.getCorners(agentID); + + var pathVec = new Array(); + for(i in 0...navPath.getPointCount()) { + pathVec.push(RecastConversions.vec4FromRecastVec3(navPath.getPoint(i))); + } + + return pathVec; + } + + public function crowdGetAgentNextTargetPath(agentID: Int): Vec4 { + if (!ready) return null; + if (recastCrowd == null) return null; + return RecastConversions.vec4FromRecastVec3(recastCrowd.getAgentNextTargetPath(agentID)); + } + public function crowdAgentGoto(agentID: Int, destination: Vec4) { if(!ready) return; if(recastCrowd == null) return; @@ -369,6 +461,31 @@ class NavMesh extends Trait { recastObstacleMap.remove(obstacleID); } + public function updateObstaclePosition(obstacleID: Int) { + if(!ready) return; + if(!tiledMesh) return; + + var navObstacle = tempObstacleMap.get(obstacleID); + if (navObstacle == null) return; + + recastNavMesh.removeObstacle(recastObstacleMap.get(obstacleID)); + + var newObstacleRef: recast.Recast.DtObstacleRef; + + if (Std.is(navObstacle, NavCylinderObstacle)) { + var cylinder = cast(navObstacle, NavCylinderObstacle); + var pos = RecastConversions.recastVec3FromVec4(cylinder.object.transform.world.getLoc()); + newObstacleRef = recastNavMesh.addCylinderObstacle(pos, cylinder.radius, cylinder.height); + } else { + var box = cast(navObstacle, NavBoxObstacle); + var pos = RecastConversions.recastVec3FromVec4(box.object.transform.world.getLoc()); + var dim = RecastConversions.recastVec3FromVec4(box.dimensions); + newObstacleRef = recastNavMesh.addBoxObstacle(pos, dim, box.angle); + } + + recastObstacleMap.set(obstacleID, newObstacleRef); + } + function fromI16(ar: kha.arrays.Int16Array, scalePos: Float): haxe.ds.Vector { var vals = new haxe.ds.Vector(Std.int(ar.length / 4) * 3); for (i in 0...Std.int(vals.length / 3)) { @@ -477,6 +594,29 @@ class NavMesh extends Trait { } + public function getNavMeshNormal(position: Vec4): Vec4 { + var Epsilon: Float = 0.01; + var P = position; + var h = getClosestPoint(P).z; + var Px = P.clone(); + Px.x += Epsilon; + var hx = getClosestPoint(Px).z; + var Py = P.clone(); + Py.y += Epsilon; + + var hy = getClosestPoint(Py).z; + + var Tx = new Vec4(Epsilon, 0, hx - h); + var Ty = new Vec4(0, Epsilon, hy - h); + var normal = new Vec4(); + normal.setFrom(Tx); + normal.cross(Ty); + + normal.normalize(); + + return normal; + } + public function drawDebugMesh(helper: DebugDrawHelper) { var recastDebugNavMesh = recastNavMesh.getDebugNavMesh(); var triangleCount = recastDebugNavMesh.getTriangleCount(); @@ -495,6 +635,11 @@ class NavMesh extends Trait { recastDebugNavMesh.delete(); #end } + + public function removeNavMesh() { + Navigation.active.navMeshes.remove(navMeshId); + } + #end } @@ -503,3 +648,5 @@ typedef MeshData = { var indices: haxe.ds.Vector; var maxIndex:Int; } + +typedef NavObstacle = iron.Trait; diff --git a/leenkx/Sources/leenkx/trait/PhysicsBreak.hx b/leenkx/Sources/leenkx/trait/PhysicsBreak.hx index 1f25983b..99ed132a 100644 --- a/leenkx/Sources/leenkx/trait/PhysicsBreak.hx +++ b/leenkx/Sources/leenkx/trait/PhysicsBreak.hx @@ -3,6 +3,7 @@ package leenkx.trait; import iron.math.Vec4; import iron.math.Mat4; import iron.Trait; +import iron.object.Object; import iron.object.MeshObject; import iron.data.MeshData; import iron.data.SceneFormat; @@ -11,6 +12,11 @@ import leenkx.trait.physics.RigidBody; import leenkx.trait.physics.PhysicsWorld; #end +import leenkx.object.MeshDataExtension; +import leenkx.object.BreakerExtension; +import kha.arrays.Float32Array; +import haxe.ds.Vector; + class PhysicsBreak extends Trait { #if (!lnx_physics) @@ -25,10 +31,34 @@ class PhysicsBreak extends Trait { var physics: PhysicsWorld; var body: RigidBody; + @prop + var fractureImpulse: Float = 0.1; + @prop + var fractureIterative: Bool = true; + @prop + var traitName: String = ''; + + @prop + var subdivideByPlane: Bool = false; + + @prop + var plane: Vec4 = new Vec4(1, 1, 1); + + @prop + var randomPlane: Bool = true; + + @prop + var scaleUV: Float = 0.3; + + @prop + var flatShading: Bool = true; + public function new() { super(); - breaker = new ConvexBreaker(); + breaker = new ConvexBreaker(0.1); + @:privateAccess breaker.scaleUV = scaleUV; + @:privateAccess breaker.flatShading = flatShading; notifyOnInit(init); } @@ -80,17 +110,50 @@ class PhysicsBreak extends Trait { } } - #if lnx_oimo - var fractureImpulse = 1.0; - #else - var fractureImpulse = 4.0; - #end - if (maxImpulse > fractureImpulse && impactPoint != null && impactNormal != null) { + if (maxImpulse > fractureImpulse) { var radialIter = 1; var randIter = 1; - var debris = breaker.subdivideByImpact(cast object, impactPoint, impactNormal, radialIter, randIter); - // var numObjects = debris.length; + var debris = subdivideByPlane ? breaker.subdivideByPlane(cast object, plane, randomPlane) : + breaker.subdivideByImpact(cast object, impactPoint, impactNormal, radialIter, randIter); for (o in debris) { + var obj: Object = cast o; + obj.name = o.data.raw.name; + obj.setParent(iron.Scene.active.root); + + var dims = new kha.arrays.Float32Array(3); + dims[0] = o.data.geom.aabb.x; + dims[1] = o.data.geom.aabb.y; + dims[2] = o.data.geom.aabb.z; + + obj.raw = cast { + type: "mesh_object", + name: obj.name, + data_ref: obj.name, + dimensions: dims + }; + + if (traitName == '') + for (t in object.traits) { + var tc = Type.getClass(t); + if (tc == null || tc == leenkx.trait.physics.RigidBody || + tc == leenkx.trait.PhysicsBreak || Type.resolveClass("leenx.trait.PhysicsBreak") != null) continue; + + var nt = Type.createInstance(tc, []); + for (f in Type.getInstanceFields(tc)) { + var v = Reflect.field(t, f); + if (v != null && !Reflect.isFunction(v)) Reflect.setField(nt, f, v); + } + obj.addTrait(nt); + } + else { + var cname = Type.resolveClass(Main.projectPackage + "." + traitName); + if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName); + var trait = Type.createInstance(cname, []); + obj.addTrait(trait); + + obj.addTrait(new leenkx.trait.internal.UniformsManager()); + } + var ud = breaker.userDataMap.get(cast o); if (ud == null) continue; var params: RigidBodyParams = { @@ -118,9 +181,13 @@ class PhysicsBreak extends Trait { lockRotationY: false, lockRotationZ: false }; - o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params)); - if (cast(o, MeshObject).data.geom.positions.values.length < 600) { - o.addTrait(new PhysicsBreak()); + + obj.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params)); + if (fractureIterative && cast(o, MeshObject).data.geom.positions.values.length > 100) { + var pb = new PhysicsBreak(); + pb.fractureImpulse = fractureImpulse / 2; + pb.traitName = traitName; + obj.addTrait(pb); } // Track debris for cleanup on scene change allDebris.push(cast o); @@ -135,737 +202,3 @@ class PhysicsBreak extends Trait { #end } - -// Based on work by yomboprime https://github.com/yomboprime -// This class can be used to subdivide a convex geometry object into pieces -class ConvexBreaker { - - var minSizeForBreak: Float; - var smallDelta: Float; - - var tempLine: Line3; - var tempPlane: Plane; - var tempPlane2: Plane; - var tempCM1: Vec4; - var tempCM2: Vec4; - var tempVec4: Vec4; - var tempVec42: Vec4; - var tempVec43: Vec4; - var tempCutResult: CutResult; - var segments: Array; - - public var userDataMap: Map; - - // minSizeForBreak Min size a debris can have to break - // smallDelta Max distance to consider that a point belongs to a plane - public function new(minSizeForBreak = 1.4, smallDelta = 0.0001) { - this.minSizeForBreak = minSizeForBreak; - this.smallDelta = smallDelta; - tempLine = new Line3(); - tempPlane = new Plane(); - tempPlane2 = new Plane(); - tempCM1 = new Vec4(); - tempCM2 = new Vec4(); - tempVec4 = new Vec4(); - tempVec42 = new Vec4(); - tempVec43 = new Vec4(); - tempCutResult = new CutResult(); - segments = new Array(); - var n = 30 * 30; - for (i in 0...n) segments.push(false); - userDataMap = new Map(); - } - - public function initBreakableObject(object: MeshObject, mass: Float, friction: Float, velocity: Vec4, angularVelocity: Vec4, breakable: Bool) { - var ar = object.data.geom.positions.values; - var scalePos = object.data.scalePos; - // Create vertices mark - var sc = object.transform.scale; - var vertices = new Array(); - for (i in 0...Std.int(ar.length / 4)) { - // Use w component as mark - vertices.push(new Vec4( - ar[i * 4 ] * sc.x * (1 / 32767) * scalePos, - ar[i * 4 + 1] * sc.y * (1 / 32767) * scalePos, - ar[i * 4 + 2] * sc.z * (1 / 32767) * scalePos, - 0 - )); - } - - var ind = object.data.geom.indices[0]; - var faces = new Array(); - for (i in 0...Std.int(ind.length / 3)) { - var a = ind[i * 3]; - var b = ind[i * 3 + 1]; - var c = ind[i * 3 + 2]; - // Merge duplis - for (f in faces) { - if (vertices[a].equals(vertices[f.a])) a = f.a; - else if (vertices[a].equals(vertices[f.b])) a = f.b; - else if (vertices[a].equals(vertices[f.c])) a = f.c; - if (vertices[b].equals(vertices[f.a])) b = f.a; - else if (vertices[b].equals(vertices[f.b])) b = f.b; - else if (vertices[b].equals(vertices[f.c])) b = f.c; - if (vertices[c].equals(vertices[f.a])) c = f.a; - else if (vertices[c].equals(vertices[f.b])) c = f.b; - else if (vertices[c].equals(vertices[f.c])) c = f.c; - } - faces.push(new Face3(a, b, c)); - } - // Reorder vertices - var verts = new Array(); - var map = new Map(); - var i = 0; - function orderVert(fi: Int): Int { - var val = map.get(fi); - if (val == null) { - verts.push(vertices[fi]); - map.set(fi, i); - i++; - return i - 1; - } - else return val; - } - for (f in faces) { - f.a = orderVert(f.a); - f.b = orderVert(f.b); - f.c = orderVert(f.c); - } - - var userData = new UserData(); - userData.mass = mass; - userData.friction = friction; - userData.velocity = velocity.clone(); - userData.angularVelocity = angularVelocity.clone(); - userData.breakable = breakable; - userData.vertices = verts; - userData.faces = faces; - userDataMap.set(object, userData); - } - - // maxRadialIterations Iterations for radial cuts - // maxRandomIterations Max random iterations for not-radial cuts - public function subdivideByImpact(object: MeshObject, pointOfImpact: Vec4, normal: Vec4, maxRadialIterations: Int, maxRandomIterations: Int): Array { - var debris: Array = []; - - tempVec4.addvecs(pointOfImpact, normal); - tempPlane.setFromCoplanarPoints(pointOfImpact, object.transform.loc, tempVec4); - - var maxTotalIterations = maxRandomIterations + maxRadialIterations; - var scope = this; - - function subdivideRadial(subObject: MeshObject, startAngle: Float, endAngle: Float, numIterations: Int) { - - if (Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations) { - debris.push(subObject); - return; - } - - var angle = Math.PI; - if (numIterations == 0) { - tempPlane2.normal.setFrom(tempPlane.normal); - tempPlane2.constant = tempPlane.constant; - } - else { - if (numIterations <= maxRadialIterations) { - angle = (endAngle - startAngle) * (0.2 + 0.6 * Math.random()) + startAngle; - - // Rotate tempPlane2 at impact point around normal axis and the angle - scope.tempVec42.setFrom(object.transform.loc).sub(pointOfImpact).applyAxisAngle(normal, angle).add(pointOfImpact); - tempPlane2.setFromCoplanarPoints(pointOfImpact, scope.tempVec4, scope.tempVec42); - } - else { - angle = ((0.5 * (numIterations & 1)) + 0.2 * (2 - Math.random())) * Math.PI; - - // Rotate tempPlane2 at object position around normal axis and the angle - scope.tempVec42.setFrom(pointOfImpact).sub(subObject.transform.loc).applyAxisAngle(normal, angle).add(subObject.transform.loc); - scope.tempVec43.setFrom(normal).add(subObject.transform.loc); - tempPlane2.setFromCoplanarPoints(subObject.transform.loc, scope.tempVec43, scope.tempVec42); - } - } - - // Perform the cut - scope.cutByPlane(subObject, tempPlane2, scope.tempCutResult); - - var object1 = scope.tempCutResult.object1; - var object2 = scope.tempCutResult.object2; - if (object1 != null) subdivideRadial(object1, startAngle, angle, numIterations + 1); - if (object2 != null) subdivideRadial(object2, angle, endAngle, numIterations + 1); - - // Object was subdivided into debris - iron.Scene.active.meshes.remove(subObject); - } - - subdivideRadial(object, 0, 2 * Math.PI, 0); - return debris; - } - - function transformFreeVector(v: Vec4, m: Mat4): Vec4 { - // Vector interpreted as a free vector - // Mat4 orthogonal matrix (matrix without scale) - var x = v.x, y = v.y, z = v.z; - v.x = m._00 * x + m._10 * y + m._20 * z; - v.y = m._01 * x + m._11 * y + m._21 * z; - v.z = m._02 * x + m._12 * y + m._22 * z; - return v; - } - - function transformFreeVectorInverse(v: Vec4, m: Mat4): Vec4 { - // Vector interpreted as a free vector - // Mat4 orthogonal matrix (matrix without scale) - var x = v.x, y = v.y, z = v.z; - v.x = m._00 * x + m._01 * y + m._02 * z; - v.y = m._10 * x + m._11 * y + m._12 * z; - v.z = m._20 * x + m._21 * y + m._22 * z; - return v; - } - - function transformTiedVectorInverse(v: Vec4, m: Mat4): Vec4 { - // Vector interpreted as a tied (ordinary) vector - // Mat4 orthogonal matrix (matrix without scale) - var x = v.x, y = v.y, z = v.z; - v.x = m._00 * x + m._01 * y + m._02 * z - m._30; - v.y = m._10 * x + m._11 * y + m._12 * z - m._31; - v.z = m._20 * x + m._21 * y + m._22 * z - m._32; - return v; - }; - - function transformPlaneToLocalSpace(plane: Plane, m: Mat4, resultPlane: Plane) { - resultPlane.normal.setFrom(plane.normal); - resultPlane.constant = plane.constant; - - var v1 = new Vec4(); - var referencePoint = transformTiedVectorInverse(plane.coplanarPoint(v1), m); - transformFreeVectorInverse(resultPlane.normal, m); - - // Recalculate constant - resultPlane.constant = -referencePoint.dot(resultPlane.normal); - } - - // Returns breakable objects, the resulting 2 pieces of the cut - // object2 can be null if the plane doesn't cut the object - // object1 can be null only in case of error - // Returned value is number of pieces, 0 for error - function cutByPlane(object: MeshObject, plane: Plane, output: CutResult): Int { - var userData = userDataMap.get(object); - var points: Array = userData.vertices; - var faces: Array = userData.faces; - - var numPoints = points.length; - var points1 = []; - var points2 = []; - var delta = smallDelta; - - // Reset vertices mark - for (i in 0...numPoints) points[i].w = 0; - - // Reset segments mark - var numPointPairs = numPoints * numPoints; - for (i in 0...numPointPairs) this.segments[i] = false; - - // Iterate through the faces to mark edges shared by coplanar faces - for (i in 0...faces.length - 1) { - var face1 = faces[i]; - - for (j in (i + 1)...faces.length) { - var face2 = faces[j]; - var coplanar = 1 - face1.normal.dot(face2.normal) < delta; - - if (coplanar) { - var a1 = face1.a; - var b1 = face1.b; - var c1 = face1.c; - var a2 = face2.a; - var b2 = face2.b; - var c2 = face2.c; - - if (a1 == a2 || a1 == b2 || a1 == c2) { - if (b1 == a2 || b1 == b2 || b1 == c2) { - this.segments[a1 * numPoints + b1] = true; - this.segments[b1 * numPoints + a1] = true; - } - else { - this.segments[c1 * numPoints + a1] = true; - this.segments[a1 * numPoints + c1] = true; - } - } - else if (b1 == a2 || b1 == b2 || b1 == c2) { - this.segments[c1 * numPoints + b1] = true; - this.segments[b1 * numPoints + c1] = true; - } - } - } - } - - // Transform the plane to object local space - var localPlane = this.tempPlane; - object.transform.buildMatrix(); - transformPlaneToLocalSpace(plane, object.transform.world, localPlane); - - // Iterate through the faces adding points to both pieces - for (i in 0...faces.length) { - - var face = faces[i]; - for (segment in 0...3) { - var i0 = segment == 0 ? face.a : (segment == 1 ? face.b : face.c); - var i1 = segment == 0 ? face.b : (segment == 1 ? face.c : face.a); - - var segmentState = this.segments[i0 * numPoints + i1]; - // The segment already has been processed in another face - if (segmentState) continue; - - // Mark segment as processed (also inverted segment) - this.segments[i0 * numPoints + i1] = true; - this.segments[i1 * numPoints + i0] = true; - - var p0 = points[i0]; - var p1 = points[i1]; - - if (p0.w == 0) { - var d = localPlane.distanceToPoint(p0); - - // mark: 1 for negative side, 2 for positive side, 3 for coplanar point - if (d > delta) { - p0.w = 2; - points2.push(p0); - } - else if (d < -delta) { - p0.w = 1; - points1.push(p0); - } - else { - p0.w = 3; - points1.push(p0); - var p02 = p0.clone(); - p02.w = 3; - points2.push(p02); - } - } - - if (p1.w == 0) { - var d = localPlane.distanceToPoint(p1); - - // mark: 1 for negative side, 2 for positive side, 3 for coplanar point - if (d > delta) { - p1.w = 2; - points2.push(p1); - } - else if (d < -delta) { - p1.w = 1; - points1.push(p1); - } - else { - p1.w = 3; - points1.push(p1); - var p1_2 = p1.clone(); - p1_2.w = 3; - points2.push(p1_2); - } - } - - var mark0 = p0.w; - var mark1 = p1.w; - - if ((mark0 == 1 && mark1 == 2 ) || ( mark0 == 2 && mark1 == 1)) { - // Intersection of segment with the plane - tempLine.start.setFrom(p0); - tempLine.end.setFrom(p1); - var intersection = localPlane.intersectLine(tempLine); - if (intersection == null) return 0; - - intersection.w = 1; - points1.push(intersection); - var intersection_2 = intersection.clone(); - intersection_2.w = 2; - points2.push(intersection_2); - } - } - } - - // Calculate debris mass (very fast and imprecise): - var newMass = userData.mass * 0.5; - - // Calculate debris Center of Mass (again fast and imprecise) - tempCM1.set(0, 0, 0); - var radius1 = 0.0; - var numPoints1 = points1.length; - if (numPoints1 > 0) { - for (i in 0...numPoints1) { - tempCM1.add(points1[i]); - } - tempCM1.mult(1.0 / numPoints1); - for (i in 0...numPoints1) { - var p = points1[i]; - p.sub(tempCM1); - radius1 = Math.max(Math.max(radius1, p.x), Math.max(p.y, p.z)); - } - tempCM1.add(object.transform.loc); - } - - tempCM2.set(0, 0, 0); - var radius2 = 0.0; - var numPoints2 = points2.length; - if (numPoints2 > 0) { - for (i in 0...numPoints2) { - tempCM2.add(points2[i]); - } - tempCM2.mult(1.0 / numPoints2); - for (i in 0...numPoints2) { - var p = points2[i]; - p.sub(tempCM2); - radius2 = Math.max(Math.max(radius2, p.x), Math.max(p.y, p.z)); - } - tempCM2.add(object.transform.loc); - } - - var object1 = null; - var object2 = null; - var numObjects = 0; - if (numPoints1 > 4) { - var data1 = makeMeshData(points1); - if (data1 != null) { - object1 = new MeshObject(data1, object.materials); - object1.transform.loc.setFrom(tempCM1); - object1.transform.rot.setFrom(object.transform.rot); - object1.transform.buildMatrix(); - initBreakableObject(object1, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius1 > minSizeForBreak); - numObjects++; - } - } - - if (numPoints2 > 4) { - var data2 = makeMeshData(points2); - if (data2 != null) { - object2 = new MeshObject(data2, object.materials); - object2.transform.loc.setFrom(tempCM2); - object2.transform.rot.setFrom(object.transform.rot); - object2.transform.buildMatrix(); - initBreakableObject(object2, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius2 > minSizeForBreak); - numObjects++; - } - } - - output.object1 = object1; - output.object2 = object2; - return numObjects; - } - - static var meshIndex = 0; - function makeMeshData(points: Array): MeshData { - // Need at least 4 points for a 3D hull - if (points.length < 4) return null; - - while (points.length > 50) points.pop(); - var cm = new ConvexHull(points); - - // Validate hull has enough geometry for a mesh - if (cm.vertices.length < 4 || cm.face3s.length < 4) return null; - - var maxdim = 1.0; - var pa = new Array(); - var na = new Array(); - for (p in cm.vertices) { - pa.push(p.x); - pa.push(p.y); - pa.push(p.z); - na.push(0.0); - na.push(0.0); - na.push(0.0); - - var ax = Math.abs(p.x); - var ay = Math.abs(p.y); - var az = Math.abs(p.z); - if (ax > maxdim) maxdim = ax; - if (ay > maxdim) maxdim = ay; - if (az > maxdim) maxdim = az; - } - maxdim *= 2; - - var ind = new Array(); - function addFlatNormal(normal: Vec4, fi: Int) { - if (na[fi * 3] != 0.0 || na[fi * 3 + 1] != 0.0 || na[fi * 3 + 2] != 0.0) { - pa.push(pa[fi * 3 ]); - pa.push(pa[fi * 3 + 1]); - pa.push(pa[fi * 3 + 2]); - na.push(normal.x); - na.push(normal.y); - na.push(normal.z); - ind.push(Std.int(pa.length / 3 - 1)); - } - else { - na[fi * 3 ] = normal.x; - na[fi * 3 + 1] = normal.y; - na[fi * 3 + 2] = normal.z; - ind.push(fi); - } - } - for (f in cm.face3s) { - // Duplicate vertex for flat normals - addFlatNormal(f.normal, f.a); - addFlatNormal(f.normal, f.b); - addFlatNormal(f.normal, f.c); - } - - // TODO: - var n = Std.int(pa.length / 3); - var paa = new kha.arrays.Int16Array(n * 4); - var naa = new kha.arrays.Int16Array(n * 2); - var invdim = 1 / maxdim; - for (i in 0...n) { - paa.set(i * 4 , Std.int(pa[i * 3 ] * 32767 * invdim)); - paa.set(i * 4 + 1, Std.int(pa[i * 3 + 1] * 32767 * invdim)); - paa.set(i * 4 + 2, Std.int(pa[i * 3 + 2] * 32767 * invdim)); - naa.set(i * 2 , Std.int(na[i * 3 ] * 32767 * invdim)); - naa.set(i * 2 + 1, Std.int(na[i * 3 + 1] * 32767 * invdim)); - paa.set(i * 4 + 3, Std.int(na[i * 3 + 2] * 32767 * invdim)); - } - var inda = new kha.arrays.Uint32Array(ind.length); - for (i in 0...ind.length) inda.set(i, ind[i]); - - var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; - var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; - var indices: TIndexArray = { material: 0, values: inda }; - - var rawmesh: TMeshData = { - name: "TempMesh" + (meshIndex++), - sorting_index: 0, - vertex_arrays: [pos, nor], - index_arrays: [indices], - scale_pos: maxdim - }; - - // Synchronous on Krom - var md = new MeshData(rawmesh, function(d: MeshData) {}); - md.geom.calculateAABB(); - return md; - } -} - -class UserData { - - public var mass: Float; - public var friction: Float; - public var velocity: Vec4; - public var angularVelocity: Vec4; - public var breakable: Bool; - - public var vertices: Array; - public var faces: Array; - - public function new() {} -} - -class CutResult { - - public var object1: MeshObject = null; - public var object2: MeshObject = null; - public function new() {} -} - -class Line3 { - - public var start: Vec4; - public var end: Vec4; - - public function new() { - start = new Vec4(); - end = new Vec4(); - } - - public function delta(result: Vec4): Vec4 { - result.subvecs(end, start); - return result; - } -} - -class Plane { - - public var normal = new Vec4(1.0, 0.0, 0.0); - public var constant = 0.0; - - public function new() {} - - public function distanceToPoint(point: Vec4): Float { - return normal.dot(point) + constant; - } - - public function setFromCoplanarPoints(a: Vec4, b: Vec4, c: Vec4): Plane { - var v1 = new Vec4(); - var v2 = new Vec4(); - var normal = v1.subvecs(c, b).cross(v2.subvecs(a, b)).normalize(); - set(normal, a); - return this; - } - - public function set(normal: Vec4, point: Vec4): Plane { - this.normal.setFrom(normal); - constant = -point.dot(this.normal); - return this; - } - - public function coplanarPoint(result: Vec4): Vec4 { - return result.setFrom(normal).mult(-constant); - } - - public function intersectLine(line: Line3): Vec4 { - var v1 = new Vec4(); - var result = new Vec4(); - var direction = line.delta(v1); - var denominator = normal.dot(direction); - if (denominator == 0) { - // line is coplanar, return origin - if (distanceToPoint(line.start) == 0) { - return result.setFrom(line.start); - } - // Unsure if this is the correct method to handle this case. - return null; - } - - var t = -(line.start.dot(this.normal) + constant) / denominator; - if (t < 0 || t > 1) return null; - return result.setFrom(direction).mult(t).add(line.start); - } -} - -// Based on work by qiao https://github.com/qiao -// This is a convex hull generator using the incremental method -// The complexity is O(n^2) where n is the number of vertices -class ConvexHull { - - var faces = [[0, 1, 2], [0, 2, 1]]; - public var face3s = new Array(); - public var vertices = new Array(); - - public function new(vertices: Array) { - - for (i in 3...vertices.length) addPoint(i, vertices); - - // Push vertices into array, skipping those inside the hull - // Map from old vertex id to new id - var id = 0; - var newId = new Array(); - for (i in 0...vertices.length) newId.push(-1); - - for (i in 0...faces.length) { - var face = faces[i]; - for (j in 0...3) { - if (newId[face[j]] == -1) { - newId[face[j]] = id++; - this.vertices.push(vertices[face[j]]); - } - face[j] = newId[face[j]]; - } - } - - for (i in 0...faces.length) { - face3s.push(new Face3(faces[i][0], faces[i][1], faces[i][2])); - } - - computeFaceNormals(); - } - - var cb = new Vec4(); - var ab = new Vec4(); - function computeFaceNormals() { - for (f in 0...face3s.length) { - var face = face3s[f]; - var va = vertices[face.a]; - var vb = vertices[face.b]; - var vc = vertices[face.c]; - cb.subvecs(vc, vb); - ab.subvecs(va, vb); - cb.cross(ab); - cb.normalize(); - face.normal.setFrom(cb); - } - } - - function addPoint(vertexId: Int, vertices: Array) { - var vertex = vertices[vertexId].clone(); - - var mag = vertex.length(); - vertex.x += mag * randomOffset(); - vertex.y += mag * randomOffset(); - vertex.z += mag * randomOffset(); - - var hole: Array> = []; - var f = 0; - while (f < faces.length) { - var face = faces[f]; - - // For each face, if the vertex can see it, - // then we try to add the face's edges into the hole - if (visible(face, vertex, vertices)) { - for (e in 0...3) { - var edge = [face[e], face[(e + 1) % 3]]; - var boundary = true; - - // Remove duplicated edges - for (h in 0...hole.length) { - if (equalEdge(hole[h], edge)) { - hole[h] = hole[hole.length - 1]; - hole.pop(); - boundary = false; - break; - } - } - if (boundary) hole.push(edge); - } - - faces[f] = faces[faces.length - 1]; - faces.pop(); - } - else { - f++; - } - } - - // Construct the new faces formed by the edges of the hole and the vertex - for (h in 0...hole.length) { - faces.push([hole[h][0], hole[h][1], vertexId]); - } - } - - // Whether the face is visible from the vertex - function visible(face: Array, vertex: Vec4, vertices: Array): Bool { - var va = vertices[face[0]]; - var vb = vertices[face[1]]; - var vc = vertices[face[2]]; - var n = normal(va, vb, vc); - var dist = n.dot(va); // Distance from face to origin - return n.dot(vertex) >= dist; - } - - function normal(va: Vec4, vb: Vec4, vc: Vec4): Vec4 { - var cb = new Vec4(); - var ab = new Vec4(); - cb.subvecs(vc, vb); - ab.subvecs(va, vb); - cb.cross(ab); - cb.normalize(); - return cb; - } - - function equalEdge(ea: Array, eb: Array): Bool { - return ea[0] == eb[1] && ea[1] == eb[0]; - } - - function randomOffset(): Float { - return (Math.random() - 0.5) * 2 * 1e-6; - } -} - -class Face3 { - - public var a: Int; - public var b: Int; - public var c: Int; - public var normal: Vec4; - - public function new(a: Int, b: Int, c: Int) { - this.a = a; - this.b = b; - this.c = c; - normal = new Vec4(); - } -} diff --git a/leenkx/Sources/leenkx/trait/PhysicsDrag.hx b/leenkx/Sources/leenkx/trait/PhysicsDrag.hx index e80eada1..f47b45b1 100644 --- a/leenkx/Sources/leenkx/trait/PhysicsDrag.hx +++ b/leenkx/Sources/leenkx/trait/PhysicsDrag.hx @@ -34,11 +34,11 @@ class PhysicsDrag extends Trait { var pickDist:Float; var pickedBody:RigidBody = null; - static var v = new Vec4(); - static var m = Mat4.identity(); - static var first = true; - static var start = new Vec4(); - static var end = new Vec4(); + var v = new Vec4(); + var m = Mat4.identity(); + var first = true; + var start = new Vec4(); + var end = new Vec4(); #if lnx_jolt static var anchorPos:jolt.Jt.RVec3 = null; static var anchorRot:jolt.Jt.Quat = null; @@ -49,9 +49,41 @@ class PhysicsDrag extends Trait { if (first) { first = false; notifyOnUpdate(update); + notifyOnRemove(onRemove); } } + function onRemove() { + removeConstraint(); + } + + function removeConstraint() { + var physics = PhysicsWorld.active; + #if lnx_bullet + if (pickConstraint != null && physics != null) { + physics.world.removeConstraint(pickConstraint); + pickConstraint = null; + } + #elseif lnx_jolt + if (pickConstraint != null && physics != null) { + physics.physicsSystem.RemoveConstraint(pickConstraint); + pickConstraint = null; + } + if (anchorBodyId != null && physics != null) { + physics.bodyInterface.RemoveBody(anchorBodyId); + physics.bodyInterface.DestroyBody(anchorBodyId); + anchorBodyId = null; + anchorBody = null; + } + localPivot = null; + #end + if (pickedBody != null) { + pickedBody.activate(); + pickedBody = null; + } + Input.occupied = false; + } + function update() { var physics = PhysicsWorld.active; if (pickedBody != null) pickedBody.activate(); diff --git a/leenkx/Sources/leenkx/trait/SoundController.hx b/leenkx/Sources/leenkx/trait/SoundController.hx new file mode 100644 index 00000000..73af2898 --- /dev/null +++ b/leenkx/Sources/leenkx/trait/SoundController.hx @@ -0,0 +1,113 @@ +package leenkx.trait; + +import leenkx.trait.internal.SoundGenerator; +import iron.math.Vec4; +import kha.graphics2.Graphics; +import kha.Color; + +class SoundController extends iron.Trait { + + var wav: SoundGenerator; + var peaks: Array = []; + var x: Int; + var y: Int; + var width: Int; + var height: Int; + var lineCol: Color; + var waveCol: Color; + var waveBgCol: Color; + var bgCol: Color; + + public function new() { + super(); + notifyOnInit(function() { + wav = new SoundGenerator(); + + wav.addTone(440.00, 1.0); + wav.addSilence(0.5); + wav.addBridge(440.00, 880.00, 2.0); + wav.addTone(880.00, 1.0); + wav.addWave("square", 440.00, 1.0); + wav.addChord([261.63, 329.63, 392.00], 1.0); + wav.addNoise(0.5); + wav.addEnvelope(440.00, 1.5, 0.1, 0.2, 0.7, 0.3); + wav.addVibrato(440.00, 1.0, 5.0, 10.0); + wav.addSweep(200.00, 800.00, 1.0); + wav.generate(); + + }); + } + + public function play(): Void { + wav.play(); + } + + public function getAmplitude(?position: Null): Float { + if (wav == null || wav.channel == null || wav.channel.length <= 0 || peaks.length == 0) return 0.0; + var pos = position != null ? position : wav.channel.position; + var progress = Math.min(1.0, Math.max(0.0, pos / wav.channel.length)); + return peaks[Std.int(progress * (peaks.length - 1))]; + } + + public function addDraw(x: Int, y: Int, width: Int, height: Int, lineCol: Vec4, waveCol: Vec4, waveBgCol: Vec4, bgCol: Vec4): Void { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.lineCol = Color.fromFloats(lineCol.x, lineCol.y, lineCol.z, lineCol.w); + this.waveCol = Color.fromFloats(waveCol.x, waveCol.y, waveCol.z, waveCol.w); + this.waveBgCol = Color.fromFloats(waveBgCol.x, waveBgCol.y, waveBgCol.z, waveBgCol.w); + this.bgCol = Color.fromFloats(bgCol.x, bgCol.y, bgCol.z, bgCol.w); + + var data = wav.sound.uncompressedData; + + if (data != null) { + var sampleCount = data.length; + var samplesPerPixel = Std.int(sampleCount / width); + + peaks = []; + for (i in 0...width) { + var max = 0.0; + var start = i * samplesPerPixel; + var end = start + samplesPerPixel; + var mid = Std.int(start + (samplesPerPixel / 2)); + + for (j in start...end) { + if (j >= sampleCount) break; + var val = Math.abs(data.get(j)); + if (val > max) max = val; + } + + var sample = mid < sampleCount ? Math.abs(data.get(mid)) : 0.0; + peaks.push((max + sample) / 2); + } + } + + notifyOnRender2D(drawWaveform); + } + + public function removeDraw(): Void { + removeRender2D(drawWaveform); + } + + public function drawWaveform(g: Graphics): Void { + if (wav.channel == null) return; + + var len = wav.channel.length; + var progress = len > 0 ? Math.min(1.0, Math.max(0.0, wav.channel.position / len)) : 0.0; + var progressPixel = Std.int(progress * width); + var halfHeight = height / 2; + + g.color = bgCol; + g.fillRect(x, y, width, height); + + for (i in 0...peaks.length) { + g.color = (i < progressPixel) ? waveCol : waveBgCol; + var pHeight = peaks[i] * halfHeight; + g.drawLine(x + i, y + halfHeight - pHeight, x + i, y + halfHeight + pHeight, 1.0); + } + + g.color = lineCol; + g.fillRect(x + progressPixel - 1, y, 2, height); + } +} diff --git a/leenkx/Sources/leenkx/trait/internal/DebugConsole.hx b/leenkx/Sources/leenkx/trait/internal/DebugConsole.hx index 7b93302d..b61743fb 100644 --- a/leenkx/Sources/leenkx/trait/internal/DebugConsole.hx +++ b/leenkx/Sources/leenkx/trait/internal/DebugConsole.hx @@ -145,13 +145,38 @@ class DebugConsole extends Trait { var debugDrawSet = false; + var renderCb: kha.graphics4.Graphics->Int->Int->Void = null; + + override public function remove() { + selectedObject = null; + if (renderCb != null) { + RenderDraw.removeOnRender(renderCb); + renderCb = null; + } + super.remove(); + } + function selectObject(o: iron.object.Object) { selectedObject = o; if (!debugDrawSet) { debugDrawSet = true; - leenkx.trait.internal.DebugDraw.notifyOnRender(function(draw: leenkx.trait.internal.DebugDraw) { - if (selectedObject != null) draw.bounds(selectedObject.transform); + if (renderCb != null) RenderDraw.removeOnRender(renderCb); + renderCb = leenkx.trait.internal.RenderDraw.notifyOnRender(function(draw: leenkx.trait.internal.RenderDraw) { + if (selectedObject != null) + switch (Type.getClass(selectedObject)) { + case iron.object.SpeakerObject: + draw.speaker(selectedObject.transform); + case iron.object.CameraObject: + draw.camera(selectedObject.transform); + case iron.object.LightObject: + draw.light(selectedObject.transform); + case iron.object.Object: + if (@:privateAccess selectedObject.isEmpty) draw.empty(selectedObject.transform); + else draw.bounds(selectedObject.transform); + default: + draw.bounds(selectedObject.transform); + } }); } } @@ -364,8 +389,25 @@ class DebugConsole extends Trait { ui.g.color = 0xffffffff; } } - for (c in iron.Scene.active.root.children) { - drawList(Id.handle(), c); + + if (ui.panel(Id.handle({selected: false}), "Scene Objects")) { + ui.indent(); + for (c in iron.Scene.active.root.children) { + if (c.raw == null) { + drawList(Id.handle(), c); + } + } + ui.unindent(); + } + + if (ui.panel(Id.handle({selected: false}), "Dynamic Unparented Objects")) { + ui.indent(); + for (c in iron.Scene.active.root.children) { + if (c.raw != null) { + drawList(Id.handle(), c); + } + } + ui.unindent(); } ui.unindent(); @@ -394,7 +436,7 @@ class DebugConsole extends Trait { var localPos = selectedObject.transform.loc; var worldPos = selectedObject.transform.getWorldPosition(); var scale = selectedObject.transform.scale; - var rot = selectedObject.transform.rot.getEuler(); + var rot = selectedObject.transform.rot.toEulerOrdered("XYZ"); var dim = selectedObject.transform.dim; rot.mult(180 / 3.141592); var f = 0.0; @@ -456,7 +498,7 @@ class DebugConsole extends Trait { if (changed && selectedObject.name != "Scene") { rot.mult(3.141592 / 180); - selectedObject.transform.rot.fromEuler(rot.x, rot.y, rot.z); + selectedObject.transform.rot.fromEulerOrdered(rot, "XYZ"); selectedObject.transform.buildMatrix(); #if lnx_physics var rb = selectedObject.getTrait(leenkx.trait.physics.RigidBody); @@ -534,10 +576,11 @@ class DebugConsole extends Trait { } var col: Array = []; - - for (g in iron.Scene.active.raw.groups){ - if(iron.Scene.active.getGroup(g.name).indexOf(selectedObject) != -1) col.push(g.name); - } + + var groups = iron.Scene.active.raw.groups; + if (groups != null) + for (g in groups) + if(iron.Scene.active.getGroup(g.name).indexOf(selectedObject) != -1) col.push(g.name); if (col.length > 0) { ui.text("Collections: "+col.length); @@ -565,8 +608,19 @@ class DebugConsole extends Trait { selectedType = "(Light)"; var light = cast(selectedObject, iron.object.LightObject); var lightHandle = Id.handle(); + #if lnx_single_point lightHandle.value = light.data.raw.strength / 10; light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; + #else + if (light.data.raw.type == "sun"){ + lightHandle.value = light.data.raw.strength / 10; + light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; + } + else{ + lightHandle.value = light.strength / 10; + light.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; + } + #end #if lnx_shadowmap_atlas ui.text("status: " + (light.culledLight ? "culled" : "rendered")); ui.text("shadow map size: " + light.data.raw.shadowmap_size); @@ -643,6 +697,10 @@ class DebugConsole extends Trait { ui.text("Draw calls"); ui.text(iron.RenderPath.drawCalls + "", Align.Right); + ui.row(lrow); + ui.text("Draw 2D calls"); + ui.text(@:privateAccess iron.App.traitRenders2D.length + "", Align.Right); + ui.row(lrow); ui.text("Tris mesh"); ui.text(iron.RenderPath.numTrisMesh + "", Align.Right); @@ -675,6 +733,24 @@ class DebugConsole extends Trait { ui.unindent(); } + if (ui.panel(Id.handle({selected: false}), "Draw2D")) { + ui.indent(); + ui.row(lrow); + ui.text("Draw 2D calls"); + ui.text(@:privateAccess iron.App.traitRenders2D.length + "", Align.Right); + for (o in iron.Scene.active.root.children) { + if (o.traits != null) { + for (t in o.traits) { + var renders: Array = Reflect.field(t, "_render2D"); + if (t != null && renders != null) { + ui.text(o.name + " : " + Type.getClassName(Type.getClass(t)) + " (" + renders.length + ")"); + } + } + } + } + ui.unindent(); + } + #if lnx_shadowmap_atlas if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) { inline function highLightNext(color: kha.Color = null) { @@ -857,7 +933,7 @@ class DebugConsole extends Trait { ui.imageInvertY = true; #end for (rt in iron.RenderPath.active.renderTargets) { - ui.text(rt.raw.name); + ui.text(rt.raw.name+' '+rt.raw.format); if (rt.image != null && !rt.is3D) { ui.image(rt.image); } @@ -884,13 +960,23 @@ class DebugConsole extends Trait { ui.unindent(); } - // if (ui.panel(Id.handle({selected: false}), 'Cached Textures')) { - // ui.indent(); - // for (c in iron.data.Data.cachedImages) { - // ui.image(c); - // } - // ui.unindent(); - // } + if (ui.panel(Id.handle({selected: false}), 'Cached Textures')) { + ui.indent(); + for (c in iron.data.Data.cachedImages) { + ui.image(c); + } + ui.unindent(); + } + + if (ui.panel(Id.handle({selected: false}), "Console")) { + ui.indent(); + var t = ui.textInput(Id.handle()); + if (ui.button("Run")) { + try { trace("> " + t + " : " + Std.string(js.Lib.eval(t))); } + catch (e: Dynamic) { trace(e); } + } + ui.unindent(); + } } if (ui.tab(htab, lastTraces[0] == "" ? "Console" : lastTraces[0].substr(0, 20))) { #if js @@ -898,7 +984,7 @@ class DebugConsole extends Trait { ui.indent(); var t = ui.textInput(Id.handle()); if (ui.button("Run")) { - try { trace("> " + t); js.Lib.eval(t); } + try { trace("> " + t + " : " + Std.string(js.Lib.eval(t))); } catch (e: Dynamic) { trace(e); } } ui.unindent(); diff --git a/leenkx/Sources/leenkx/trait/internal/DebugDraw.hx b/leenkx/Sources/leenkx/trait/internal/DebugDraw.hx index 89f59d71..fd9c0c45 100644 --- a/leenkx/Sources/leenkx/trait/internal/DebugDraw.hx +++ b/leenkx/Sources/leenkx/trait/internal/DebugDraw.hx @@ -143,7 +143,7 @@ class DebugDraw { static var corner4 = new Vec4(); static var cameraLook = new Vec4(); public function line(x1: Float, y1: Float, z1: Float, x2: Float, y2: Float, z2: Float) { - + if (vbData == null) return; if (lines >= maxLines) { end(); begin(); } midPoint.set(x1 + x2, y1 + y2, z1 + z2); diff --git a/leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx b/leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx index 18def74f..1c13617f 100644 --- a/leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx +++ b/leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx @@ -1,11 +1,44 @@ package leenkx.trait.internal; -// To create a custom loading screen copy this file to blend_root/Sources/arm/LoadingScreen.hx +// To create a custom loading screen copy this file to blend_root/Sources/lnx/LoadingScreen.hx class LoadingScreen { + static var font: kha.Font = null; + static var fontRequested: Bool = false; + static var randomColorR: Float = 0; + static var randomColorG: Float = 0; + static var randomColorB: Float = 0; + static var isInitialized = false; + public static function render(g: kha.graphics2.Graphics, assetsLoaded: Int, assetsTotal: Int) { - g.color = 0xffcf2b43; - g.fillRect(0, iron.App.h() - 6, iron.App.w() / assetsTotal * assetsLoaded, 6); + + if (!isInitialized) { + randomColorR = Math.random(); + randomColorG = Math.random(); + randomColorB = Math.random(); + isInitialized = true; + } + + var progress = assetsLoaded / assetsTotal; + var r = progress * randomColorR; + var gg = progress * randomColorG; + var b = progress * randomColorB; + + g.color = kha.Color.fromFloats(r, gg, b, 1); + g.fillRect(0, iron.App.h() - 50, iron.App.w() / assetsTotal * assetsLoaded, 50); + + if (font == null && !fontRequested) { + fontRequested = true; + iron.data.Data.getFont('font_default.ttf', (f: kha.Font) -> { + font = f; + }); + } + else if (font != null) { + g.font = font; + g.fontSize = 50; + g.color = 0xffffffff; + g.drawString(Math.floor(progress * 100) + '%', iron.App.w() / assetsTotal * assetsLoaded - 80, iron.App.h() - 50); + } } } diff --git a/leenkx/Sources/leenkx/trait/internal/RenderDraw.hx b/leenkx/Sources/leenkx/trait/internal/RenderDraw.hx new file mode 100644 index 00000000..cdd2a21d --- /dev/null +++ b/leenkx/Sources/leenkx/trait/internal/RenderDraw.hx @@ -0,0 +1,574 @@ +package leenkx.trait.internal; + +import kha.graphics4.PipelineState; +import kha.graphics4.VertexStructure; +import kha.graphics4.VertexBuffer; +import kha.graphics4.IndexBuffer; +import kha.graphics4.VertexData; +import kha.graphics4.Usage; +import kha.graphics4.ConstantLocation; +import kha.graphics4.CompareMode; +import kha.graphics4.CullMode; +import kha.graphics4.BlendingFactor; +import iron.math.Vec4; +import iron.math.Mat4; +using leenkx.object.TransformExtension; + +class RenderDraw { + + static var inst: RenderDraw = null; + + public var color: kha.Color = 0xff00ff00; + public var strength = 0.05; + + var vertexBuffer: VertexBuffer; + var indexBuffer: IndexBuffer; + var pipeline: PipelineState; + + var vp: Mat4; + var vpID: ConstantLocation; + + var vbData: kha.arrays.Float32Array; + var ibData: kha.arrays.Uint32Array; + + static inline var maxLines = 300; + static inline var maxVertices = maxLines * 4; + static inline var maxIndices = maxLines * 6; + var lines = 0; + + function new() { + inst = this; + + var structure = new VertexStructure(); + structure.add("pos", VertexData.Float3); + structure.add("col", VertexData.Float4); + pipeline = new PipelineState(); + pipeline.inputLayout = [structure]; + #if lnx_deferred + pipeline.fragmentShader = kha.Shaders.render_line_deferred_frag; + #else + pipeline.fragmentShader = kha.Shaders.render_line_frag; + #end + pipeline.vertexShader = kha.Shaders.render_line_vert; + + pipeline.blendSource = BlendingFactor.SourceAlpha; + pipeline.blendDestination = BlendingFactor.InverseSourceAlpha; + pipeline.alphaBlendSource = BlendingFactor.SourceAlpha; + pipeline.alphaBlendDestination = BlendingFactor.InverseSourceAlpha; + + pipeline.depthWrite = true; + pipeline.depthMode = CompareMode.Less; + pipeline.cullMode = CullMode.None; + pipeline.compile(); + vpID = pipeline.getConstantLocation("ViewProjection"); + vp = Mat4.identity(); + + vertexBuffer = new VertexBuffer(maxVertices, structure, Usage.DynamicUsage); + indexBuffer = new IndexBuffer(maxIndices, Usage.DynamicUsage); + } + + static var g: kha.graphics4.Graphics; + + public static function notifyOnRender(f: RenderDraw->Void): kha.graphics4.Graphics->Int->Int->Void { + if (inst == null) inst = new RenderDraw(); + var cb = function(g4: kha.graphics4.Graphics, i: Int, len: Int) { + g = g4; + if (i == 0) inst.begin(); + f(inst); + if (i == len - 1) inst.end(); + }; + iron.RenderPath.notifyOnContext("mesh", cb); + return cb; + } + + public static function removeOnRender(cb: kha.graphics4.Graphics->Int->Int->Void) { + iron.RenderPath.removeNotifyOnContext("mesh", cb); + } + + public function empty(transform: iron.object.Transform) { + var scl = transform.scale; + var loc = transform.getWorldPosition(); + var right = transform.world.right(); + var look = transform.world.look(); + var up = transform.world.up(); + + var p1 = new Vec4(loc.x - right.x * scl.x, loc.y - right.y * scl.x, loc.z - right.z * scl.x); + var p2 = new Vec4(loc.x + right.x * scl.x, loc.y + right.y * scl.x, loc.z + right.z * scl.x); + linev(p1, p2); + + var p3 = new Vec4(loc.x - look.x * scl.y, loc.y - look.y * scl.y, loc.z - look.z * scl.y); + var p4 = new Vec4(loc.x + look.x * scl.y, loc.y + look.y * scl.y, loc.z + look.z * scl.y); + linev(p3, p4); + + var p5 = new Vec4(loc.x - up.x * scl.z, loc.y - up.y * scl.z, loc.z - up.z * scl.z); + var p6 = new Vec4(loc.x + up.x * scl.z, loc.y + up.y * scl.z, loc.z + up.z * scl.z); + linev(p5, p6); + } + + public function light(transform: iron.object.Transform) { + var segments = 12; + var r = 0.3; + var h = 2.0; + + var loc = transform.getWorldPosition(); + var right = transform.world.right(); + var look = transform.world.look(); + var up = transform.world.up(); + + var bottom = new Vec4( + loc.x - up.x * h, + loc.y - up.y * h, + loc.z - up.z * h + ); + + linev(loc, bottom); + + var prev = new Vec4(); + var first = new Vec4(); + + for (i in 0...segments) { + if (i % 2 != 0) continue; + + var angle1 = (i / segments) * Math.PI * 2; + var angle2 = ((i + 1) / segments) * Math.PI * 2; + + var x1 = Math.cos(angle1) * r; + var y1 = Math.sin(angle1) * r; + var x2 = Math.cos(angle2) * r; + var y2 = Math.sin(angle2) * r; + + var p1 = new Vec4( + loc.x + right.x * x1 + look.x * y1, + loc.y + right.y * x1 + look.y * y1, + loc.z + right.z * x1 + look.z * y1 + ); + + var p2 = new Vec4( + loc.x + right.x * x2 + look.x * y2, + loc.y + right.y * x2 + look.y * y2, + loc.z + right.z * x2 + look.z * y2 + ); + + linev(p1, p2); + } + } + + public function camera(transform: iron.object.Transform) { + var scl = transform.scale; + var d = 2.0 * scl.z; + var w = 0.8 * scl.x; + var h = 0.6 * scl.y; + var th = 0.4 * scl.y; + + var loc = transform.getWorldPosition(); + var right = transform.world.right(); + var look = transform.world.look(); + var up = transform.world.up(); + + var c1 = new Vec4( + loc.x - up.x * d + right.x * w + look.x * h, + loc.y - up.y * d + right.y * w + look.y * h, + loc.z - up.z * d + right.z * w + look.z * h + ); + var c2 = new Vec4( + loc.x - up.x * d - right.x * w + look.x * h, + loc.y - up.y * d - right.y * w + look.y * h, + loc.z - up.z * d - right.z * w + look.z * h + ); + var c3 = new Vec4( + loc.x - up.x * d - right.x * w - look.x * h, + loc.y - up.y * d - right.y * w - look.y * h, + loc.z - up.z * d - right.z * w - look.z * h + ); + var c4 = new Vec4( + loc.x - up.x * d + right.x * w - look.x * h, + loc.y - up.y * d + right.y * w - look.y * h, + loc.z - up.z * d + right.z * w - look.z * h + ); + + var topPeak = new Vec4( + loc.x - up.x * d + look.x * (h + th), + loc.y - up.y * d + look.y * (h + th), + loc.z - up.z * d + look.z * (h + th) + ); + + linev(loc, c1); + linev(loc, c2); + linev(loc, c3); + linev(loc, c4); + + linev(c1, c2); + linev(c2, c3); + linev(c3, c4); + linev(c4, c1); + + linev(c1, topPeak); + linev(c2, topPeak); + } + + public function speaker(transform: iron.object.Transform) { + var segments = 8; + var r1 = 0.4; + var r2 = 0.2; + var h1 = 0.2; + var h2 = 0.2; + + var loc = transform.getWorldPosition(); + var right = transform.world.right(); + var look = transform.world.look(); + var up = transform.world.up(); + + var prevBase = new Vec4(); + var prevMid = new Vec4(); + var prevTop = new Vec4(); + var firstBase = new Vec4(); + var firstMid = new Vec4(); + var firstTop = new Vec4(); + + for (i in 0...segments) { + var angle = (i / segments) * Math.PI * 2; + var cos = Math.cos(angle); + var sin = Math.sin(angle); + + var mx = cos * r2; + var my = sin * r2; + + var bx = cos * r1; + var by = sin * r1; + + var currTop = new Vec4( + loc.x + right.x * mx + look.x * my + up.x * h1, + loc.y + right.y * mx + look.y * my + up.y * h1, + loc.z + right.z * mx + look.z * my + up.z * h1 + ); + + var currMid = new Vec4( + loc.x + right.x * mx + look.x * my, + loc.y + right.y * mx + look.y * my, + loc.z + right.z * mx + look.z * my + ); + + var currBase = new Vec4( + loc.x + right.x * bx + look.x * by - up.x * h2, + loc.y + right.y * bx + look.y * by - up.y * h2, + loc.z + right.z * bx + look.z * by - up.z * h2 + ); + + linev(currTop, currMid); + linev(currMid, currBase); + + if (i > 0) { + linev(prevBase, currBase); + linev(prevMid, currMid); + linev(prevTop, currTop); + } else { + firstBase.setFrom(currBase); + firstMid.setFrom(currMid); + firstTop.setFrom(currTop); + } + + prevBase.setFrom(currBase); + prevMid.setFrom(currMid); + prevTop.setFrom(currTop); + } + + linev(prevBase, firstBase); + linev(prevMid, firstMid); + linev(prevTop, firstTop); + } + + static var wv1 = new iron.math.Vec4(); + static var wv2 = new iron.math.Vec4(); + static var wv3 = new iron.math.Vec4(); + static var we1 = new iron.math.Vec4(); + static var we2 = new iron.math.Vec4(); + static var wn = new iron.math.Vec4(); + public function wireframe(mesh: iron.object.MeshObject) { + if (mesh == null || mesh.data == null) return; + + var geom = mesh.data.geom; + var pos = geom.positions.values; + var indices = geom.indices; + var world = mesh.transform.world; + var scl = mesh.data.scalePos / 32767; + + var edgeMap = new haxe.ds.StringMap(); + + for (indexArray in indices) { + var i = 0; + while (i < indexArray.length) { + var i1 = indexArray[i]; + var i2 = indexArray[i + 1]; + var i3 = indexArray[i + 2]; + + wv1.set(pos[i1 * 4], pos[i1 * 4 + 1], pos[i1 * 4 + 2]); + wv2.set(pos[i2 * 4], pos[i2 * 4 + 1], pos[i2 * 4 + 2]); + wv3.set(pos[i3 * 4], pos[i3 * 4 + 1], pos[i3 * 4 + 2]); + + we1.set(wv2.x - wv1.x, wv2.y - wv1.y, wv2.z - wv1.z); + we2.set(wv3.x - wv1.x, wv3.y - wv1.y, wv3.z - wv1.z); + + wn.crossvecs(we1, we2); + wn.normalize(); + + var edgeIndices = [[i1, i2], [i2, i3], [i3, i1]]; + for (edge in edgeIndices) { + var a = edge[0]; + var b = edge[1]; + var key = a < b ? a + "_" + b : b + "_" + a; + + if (edgeMap.exists(key)) { + if (wn.dot(edgeMap.get(key)) < 0.99) { + wv1.set(pos[a * 4] * scl, pos[a * 4 + 1] * scl, pos[a * 4 + 2] * scl).applymat4(world); + wv2.set(pos[b * 4] * scl, pos[b * 4 + 1] * scl, pos[b * 4 + 2] * scl).applymat4(world); + linev(wv1, wv2); + } + edgeMap.remove(key); + } else { + edgeMap.set(key, wn.clone()); + } + } + i += 3; + } + } + + for (key in edgeMap.keys()) { + var v = key.split("_"); + var a = Std.parseInt(v[0]); + var b = Std.parseInt(v[1]); + wv1.set(pos[a * 4] * scl, pos[a * 4 + 1] * scl, pos[a * 4 + 2] * scl).applymat4(world); + wv2.set(pos[b * 4] * scl, pos[b * 4 + 1] * scl, pos[b * 4 + 2] * scl).applymat4(world); + linev(wv1, wv2); + } + } + + public function outline(mesh: iron.object.MeshObject) { + if (mesh == null || mesh.data == null) return; + + var geom = mesh.data.geom; + var pos = geom.positions.values; + var indices = geom.indices; + var world = mesh.transform.world; + var scl = mesh.data.scalePos / 32767; + + var camera = iron.Scene.active.camera; + cameraPos.setFrom(camera.transform.getWorldPosition()); + + var faceIsFront = new Array(); + var edgeToFace = new haxe.ds.StringMap(); + var edgesToDraw = new Array(); + + for (matIndex in 0...indices.length) { + var indexArray = indices[matIndex]; + var i = 0; + var faceIdx = 0; + + while (i < indexArray.length) { + var i1 = indexArray[i]; + var i2 = indexArray[i + 1]; + var i3 = indexArray[i + 2]; + + wv1.set(pos[i1 * 4] * scl, pos[i1 * 4 + 1] * scl, pos[i1 * 4 + 2] * scl).applymat4(world); + wv2.set(pos[i2 * 4] * scl, pos[i2 * 4 + 1] * scl, pos[i2 * 4 + 2] * scl).applymat4(world); + wv3.set(pos[i3 * 4] * scl, pos[i3 * 4 + 1] * scl, pos[i3 * 4 + 2] * scl).applymat4(world); + + we1.set(wv2.x - wv1.x, wv2.y - wv1.y, wv2.z - wv1.z); + we2.set(wv3.x - wv1.x, wv3.y - wv1.y, wv3.z - wv1.z); + wn.crossvecs(we1, we2); + + viewVec.set(cameraPos.x - wv1.x, cameraPos.y - wv1.y, cameraPos.z - wv1.z); + + var isFront = wn.dot(viewVec) > 0; + faceIsFront.push(isFront); + + var eIdx = [[i1, i2], [i2, i3], [i3, i1]]; + for (e in eIdx) { + var v1x = Math.round(pos[e[0]*4] * 100); + var v1y = Math.round(pos[e[0]*4+1] * 100); + var v1z = Math.round(pos[e[0]*4+2] * 100); + var v2x = Math.round(pos[e[1]*4] * 100); + var v2y = Math.round(pos[e[1]*4+1] * 100); + var v2z = Math.round(pos[e[1]*4+2] * 100); + + var key = v1x < v2x ? v1x+"_"+v1y+"_"+v1z+"_"+v2x+"_"+v2y+"_"+v2z : v2x+"_"+v2y+"_"+v2z+"_"+v1x+"_"+v1y+"_"+v1z; + + if (edgeToFace.exists(key)) { + var otherFaceIdx = edgeToFace.get(key); + + if (faceIsFront[otherFaceIdx] != isFront) { + edgesToDraw.push(e[0]); + edgesToDraw.push(e[1]); + } + edgeToFace.remove(key); + } else { + edgeToFace.set(key, faceIdx); + } + } + + faceIdx++; + i += 3; + } + } + + var j = 0; + while (j < edgesToDraw.length) { + var a = edgesToDraw[j]; + var b = edgesToDraw[j + 1]; + wv1.set(pos[a * 4] * scl, pos[a * 4 + 1] * scl, pos[a * 4 + 2] * scl).applymat4(world); + wv2.set(pos[b * 4] * scl, pos[b * 4 + 1] * scl, pos[b * 4 + 2] * scl).applymat4(world); + linev(wv1, wv2); + j += 2; + } + } + + static var objPosition: Vec4; + static var vx = new Vec4(); + static var vy = new Vec4(); + static var vz = new Vec4(); + public function bounds(transform: iron.object.Transform) { + objPosition = transform.getWorldPosition(); + var dx = transform.dim.x / 2; + var dy = transform.dim.y / 2; + var dz = transform.dim.z / 2; + + var up = transform.world.up(); + var look = transform.world.look(); + var right = transform.world.right(); + up.normalize(); + look.normalize(); + right.normalize(); + + vx.setFrom(right); + vx.mult(dx); + vy.setFrom(look); + vy.mult(dy); + vz.setFrom(up); + vz.mult(dz); + + lineb(-1, -1, -1, 1, -1, -1); + lineb(-1, 1, -1, 1, 1, -1); + lineb(-1, -1, 1, 1, -1, 1); + lineb(-1, 1, 1, 1, 1, 1); + + lineb(-1, -1, -1, -1, 1, -1); + lineb(-1, -1, 1, -1, 1, 1); + lineb( 1, -1, -1, 1, 1, -1); + lineb( 1, -1, 1, 1, 1, 1); + + lineb(-1, -1, -1, -1, -1, 1); + lineb(-1, 1, -1, -1, 1, 1); + lineb( 1, -1, -1, 1, -1, 1); + lineb( 1, 1, -1, 1, 1, 1); + } + + static var v1 = new Vec4(); + static var v2 = new Vec4(); + static var t = new Vec4(); + function lineb(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) { + v1.setFrom(objPosition); + t.setFrom(vx); t.mult(a); v1.add(t); + t.setFrom(vy); t.mult(b); v1.add(t); + t.setFrom(vz); t.mult(c); v1.add(t); + + v2.setFrom(objPosition); + t.setFrom(vx); t.mult(d); v2.add(t); + t.setFrom(vy); t.mult(e); v2.add(t); + t.setFrom(vz); t.mult(f); v2.add(t); + + linev(v1, v2); + } + + public inline function linev(v1: Vec4, v2: Vec4) { + line(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z); + } + + static var v1c = new iron.math.Vec4(); + static var cameraPos = new iron.math.Vec4(); + static var lineVec = new iron.math.Vec4(); + static var viewVec = new iron.math.Vec4(); + static var perp = new iron.math.Vec4(); + static var corner1 = new iron.math.Vec4(); + static var corner2 = new iron.math.Vec4(); + static var corner3 = new iron.math.Vec4(); + static var corner4 = new iron.math.Vec4(); + + public function line(x1: Float, y1: Float, z1: Float, x2: Float, y2: Float, z2: Float) { + if (vbData == null) return; + if (lines >= maxLines) { end(); begin(); } + + var camera = iron.Scene.active.camera; + cameraPos.setFrom(camera.transform.getWorldPosition()); + + v1c.set(x1, y1, z1, 1.0).applymat4(camera.VP); + var dist1 = v1c.w; + v1c.set(x2, y2, z2, 1.0).applymat4(camera.VP); + var dist2 = v1c.w; + + lineVec.set(x2 - x1, y2 - y1, z2 - z1); + lineVec.normalize(); + + viewVec.set(cameraPos.x - x1, cameraPos.y - y1, cameraPos.z - z1); + viewVec.normalize(); + + perp.crossvecs(viewVec, lineVec); + perp.normalize(); + + var s1 = strength * dist1 * 0.01; + var s2 = strength * dist2 * 0.01; + + var bias = 0.005; + var bX = viewVec.x * bias; + var bY = viewVec.y * bias; + var bZ = viewVec.z * bias; + + corner1.set(x1 + perp.x * s1 + bX, y1 + perp.y * s1 + bY, z1 + perp.z * s1 + bZ); + corner2.set(x1 - perp.x * s1 + bX, y1 - perp.y * s1 + bY, z1 - perp.z * s1 + bZ); + corner3.set(x2 - perp.x * s2 + bX, y2 - perp.y * s2 + bY, z2 - perp.z * s2 + bZ); + corner4.set(x2 + perp.x * s2 + bX, y2 + perp.y * s2 + bY, z2 + perp.z * s2 + bZ); + + var i = lines * 28; + addVbData(i, [corner1.x, corner1.y, corner1.z, color.R, color.G, color.B, color.A]); + i += 7; + addVbData(i, [corner2.x, corner2.y, corner2.z, color.R, color.G, color.B, color.A]); + i += 7; + addVbData(i, [corner3.x, corner3.y, corner3.z, color.R, color.G, color.B, color.A]); + i += 7; + addVbData(i, [corner4.x, corner4.y, corner4.z, color.R, color.G, color.B, color.A]); + + i = lines * 6; + ibData[i] = lines * 4; + ibData[i + 1] = lines * 4 + 1; + ibData[i + 2] = lines * 4 + 2; + ibData[i + 3] = lines * 4 + 2; + ibData[i + 4] = lines * 4 + 3; + ibData[i + 5] = lines * 4; + + lines++; + } + + function begin() { + lines = 0; + vbData = vertexBuffer.lock(); + ibData = indexBuffer.lock(); + } + + function end() { + vertexBuffer.unlock(); + indexBuffer.unlock(); + + g.setVertexBuffer(vertexBuffer); + g.setIndexBuffer(indexBuffer); + g.setPipeline(pipeline); + var camera = iron.Scene.active.camera; + vp.setFrom(camera.V); + vp.multmat(camera.P); + g.setMatrix(vpID, vp.self); + g.drawIndexedVertices(0, lines * 6); + } + + inline function addVbData(i: Int, data: Array) { + for (offset in 0...7) { + vbData.set(i + offset, data[offset]); + } + } +} diff --git a/leenkx/Sources/leenkx/trait/internal/SoundGenerator.hx b/leenkx/Sources/leenkx/trait/internal/SoundGenerator.hx new file mode 100644 index 00000000..3eb86b2e --- /dev/null +++ b/leenkx/Sources/leenkx/trait/internal/SoundGenerator.hx @@ -0,0 +1,474 @@ +package leenkx.trait.internal; + +import haxe.io.Bytes; +import haxe.io.BytesOutput; +import iron.format.wav.Writer; +import iron.format.wav.Data; +import kha.Sound; +import kha.arrays.Float32Array; +import iron.system.Audio; + +class SoundGenerator { + var samplingRate: Int; + var channels: Int; + var bitsPerSample: Int; + var byteData: BytesOutput; + + public var sound: Sound; + public var channel: kha.audio1.AudioChannel = null; + + public static inline var SOLF_174: Float = 174.0; + public static inline var SOLF_285: Float = 285.0; + public static inline var SOLF_396: Float = 396.0; + public static inline var SOLF_417: Float = 417.0; + public static inline var SOLF_528: Float = 528.0; + public static inline var SOLF_639: Float = 639.0; + public static inline var SOLF_741: Float = 741.0; + public static inline var SOLF_852: Float = 852.0; + public static inline var SOLF_963: Float = 963.0; + + public static inline var DO3: Float = 130.81; + public static inline var DO_S3: Float = 138.59; + public static inline var RE3: Float = 146.83; + public static inline var RE_S3: Float = 155.56; + public static inline var MI3: Float = 164.81; + public static inline var FA3: Float = 174.61; + public static inline var FA_S3: Float = 185.00; + public static inline var SOL3: Float = 196.00; + public static inline var SOL_S3: Float = 207.65; + public static inline var LA3: Float = 220.00; + public static inline var LA_S3: Float = 233.08; + public static inline var SI3: Float = 246.94; + + public static inline var DO4: Float = 261.63; + public static inline var DO_S4: Float = 277.18; + public static inline var RE4: Float = 293.66; + public static inline var RE_S4: Float = 311.13; + public static inline var MI4: Float = 329.63; + public static inline var FA4: Float = 349.23; + public static inline var FA_S4: Float = 369.99; + public static inline var SOL4: Float = 392.00; + public static inline var SOL_S4: Float = 415.30; + public static inline var LA4: Float = 440.00; + public static inline var LA_S4: Float = 466.16; + public static inline var SI4: Float = 493.88; + + public static inline var DO5: Float = 523.25; + public static inline var DO_S5: Float = 554.37; + public static inline var RE5: Float = 587.33; + public static inline var RE_S5: Float = 622.25; + public static inline var MI5: Float = 659.25; + public static inline var FA5: Float = 698.46; + public static inline var FA_S5: Float = 739.99; + public static inline var SOL5: Float = 783.99; + public static inline var SOL_S5: Float = 830.61; + public static inline var LA5: Float = 880.00; + public static inline var LA_S5: Float = 932.33; + public static inline var SI5: Float = 987.77; + + public static function noteToFreq(midiNote: Int, baseA4: Float = 440.0): Float { + return baseA4 * Math.pow(2.0, (midiNote - 69) / 12.0); + } + + public static function semitonesToFreq(semitones: Int, baseA4: Float = 440.0): Float { + return baseA4 * Math.pow(2.0, semitones / 12.0); + } + + public function new(samplingRate: Int = 44100, channels: Int = 1, bitsPerSample: Int = 16) { + this.samplingRate = samplingRate; + this.channels = channels; + this.bitsPerSample = bitsPerSample; + byteData = new BytesOutput(); + } + + public function generate(): Void { + if (byteData == null) return; + + var rawBytes = byteData.getBytes(); + var totalSamples = Std.int(rawBytes.length / (bitsPerSample / 8)); + sound = new Sound(); + sound.uncompressedData = new Float32Array(totalSamples); + sound.sampleRate = samplingRate; + sound.channels = channels; + + for (i in 0...totalSamples) { + if (bitsPerSample == 16) { + var pos = i * 2; + var val = rawBytes.get(pos) | (rawBytes.get(pos + 1) << 8); + if ((val & 0x8000) != 0) val -= 0x10000; + sound.uncompressedData[i] = val / 32767.0; + } else if (bitsPerSample == 8) { + var val = rawBytes.get(i) - 128; + sound.uncompressedData[i] = val / 127.0; + } + } + } + + public function play(loop: Bool = false, stream = false, volume: Float = 1.0): Void { + if (sound != null){ + channel = Audio.play(sound, loop, stream); + channel.volume = volume; + } + } + + public function stop(): Void { + if (channel != null) channel.stop(); + } + + public function pause(): Void { + if (channel != null) channel.pause(); + } + + public function setVolume(volume: Float): Void { + if (channel != null) channel.volume = volume; + } + + public function setPitch(pitch: Float): Void { + if (sound != null) sound.sampleRate = Std.int(samplingRate * pitch); + } + + public function write(file: String): Void { + if (byteData == null) return; + + var rawBytes = byteData.getBytes(); + var output = new BytesOutput(); + var writer = new Writer(output); + + var wavData: WAVE = { + header: { + format: WAVEFormat.WF_PCM, + channels: channels, + samplingRate: samplingRate, + bitsPerSample: bitsPerSample, + byteRate: Std.int(samplingRate * channels * bitsPerSample / 8), + blockAlign: Std.int(channels * bitsPerSample / 8) + }, + data: rawBytes, + cuePoints: [] + }; + + writer.write(wavData); + + #if kha_krom + Krom.fileSaveBytes(Krom.getFilesLocation() + "/" + file, output.getBytes().getData()); + + #elseif kha_html5 + var blob = new js.html.Blob([output.getBytes().getData()], {type: "application"}); + var url = js.html.URL.createObjectURL(blob); + var a = cast(js.Browser.document.createElement("a"), js.html.AnchorElement); + a.href = url; + a.download = file; + a.click(); + js.html.URL.revokeObjectURL(url); + #end + } + + private function writeSample(value: Float): Void { + if (bitsPerSample == 16) { + var sampleVal = Std.int(value); + byteData.writeInt16(sampleVal); + if (channels > 1) byteData.writeInt16(sampleVal); + } else if (bitsPerSample == 8) { + var sampleVal = Std.int(value) + 128; + byteData.writeByte(sampleVal); + if (channels > 1) byteData.writeByte(sampleVal); + } + } + + public function addTone(frequency: Float, duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + + for (i in 0...numSamples) { + var t = i / samplingRate; + var value = Math.sin(2 * Math.PI * frequency * t) * amplitude; + writeSample(value); + } + } + + public function addSilence(duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + for (i in 0...numSamples) { + writeSample(0.0); + } + } + + public function addBridge(startFreq: Float, endFreq: Float, duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + + for (i in 0...numSamples) { + var progress = i / numSamples; + var currentFreq = startFreq + (endFreq - startFreq) * progress; + var t = i / samplingRate; + var value = Math.sin(2 * Math.PI * currentFreq * t) * amplitude; + writeSample(value); + } + } + + public function addWave(waveType: String, frequency: Float, duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + + for (i in 0...numSamples) { + var t = i / samplingRate; + var phase = (t * frequency) - Math.floor(t * frequency); + var sampleVal: Float = 0.0; + + switch (waveType) { + case "square": + sampleVal = (phase < 0.5) ? amplitude : -amplitude; + case "sawtooth": + sampleVal = (2.0 * phase - 1.0) * amplitude; + case "triangle": + sampleVal = (Math.abs(4.0 * phase - 2.0) - 1.0) * amplitude; + default: + sampleVal = Math.sin(2 * Math.PI * frequency * t) * amplitude; + } + + writeSample(sampleVal); + } + } + + public function addChord(frequencies: Array, duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + var numFreqs = frequencies.length; + if (numFreqs == 0) return; + + for (i in 0...numSamples) { + var t = i / samplingRate; + var sampleVal: Float = 0.0; + for (f in frequencies) { + sampleVal += Math.sin(2 * Math.PI * f * t); + } + sampleVal = (sampleVal / numFreqs) * amplitude; + writeSample(sampleVal); + } + } + + public function addNoise(duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + + for (i in 0...numSamples) { + var sampleVal = (Math.random() * 2.0 - 1.0) * amplitude; + writeSample(sampleVal); + } + } + + public function addEnvelope(frequency: Float, duration: Float, attack: Float, decay: Float, sustain: Float, release: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + + var attackSamples = Std.int(attack * samplingRate); + var decaySamples = Std.int(decay * samplingRate); + var releaseSamples = Std.int(release * samplingRate); + var sustainSamples = numSamples - attackSamples - decaySamples - releaseSamples; + if (sustainSamples < 0) sustainSamples = 0; + + for (i in 0...numSamples) { + var t = i / samplingRate; + var env: Float = 0.0; + + if (i < attackSamples) { + env = i / attackSamples; + } else if (i < attackSamples + decaySamples) { + var progress = (i - attackSamples) / decaySamples; + env = 1.0 - progress * (1.0 - sustain); + } else if (i < attackSamples + decaySamples + sustainSamples) { + env = sustain; + } else { + var progress = (i - attackSamples - decaySamples - sustainSamples) / releaseSamples; + env = sustain * (1.0 - progress); + if (env < 0.0) env = 0.0; + } + + var sampleVal = Math.sin(2 * Math.PI * frequency * t) * amplitude * env; + writeSample(sampleVal); + } + } + + public function addVibrato(frequency: Float, duration: Float, lfoRate: Float, lfoDepth: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + var currentPhase: Float = 0.0; + + for (i in 0...numSamples) { + var t = i / samplingRate; + var freqOffset = Math.sin(2 * Math.PI * lfoRate * t) * lfoDepth; + var currentFreq = frequency + freqOffset; + + currentPhase += (2 * Math.PI * currentFreq) / samplingRate; + var sampleVal = Math.sin(currentPhase) * amplitude; + writeSample(sampleVal); + } + } + + public function addSweep(startFreq: Float, endFreq: Float, duration: Float): Void { + if (byteData == null) return; + + var numSamples = Std.int(samplingRate * duration); + var amplitude = (bitsPerSample == 8) ? 127.0 : 16384.0; + var currentPhase: Float = 0.0; + + for (i in 0...numSamples) { + var progress = i / numSamples; + var currentFreq = startFreq * Math.pow(endFreq / startFreq, progress); + + currentPhase += (2 * Math.PI * currentFreq) / samplingRate; + var sampleVal = Math.sin(currentPhase) * amplitude; + writeSample(sampleVal); + } + } + + public function playDSPFilter(mode: String, freq1: Float, freq2: Float = 0.0): Void { + if (sound == null || sound.uncompressedData == null) return; + + var dt = 1.0 / samplingRate; + var lastOutput: Float = 0.0; + var lastInput: Float = 0.0; + var lastLow: Float = 0.0; + var lastHigh: Float = 0.0; + var sampleIndex = 0; + var data = sound.uncompressedData; + + var alphaLow = dt / ((1.0 / (2.0 * Math.PI * freq1)) + dt); + var alphaHigh = (1.0 / (2.0 * Math.PI * freq1)) / ((1.0 / (2.0 * Math.PI * freq1)) + dt); + var alphaBandLow = dt / ((1.0 / (2.0 * Math.PI * freq2)) + dt); + + kha.audio2.Audio.audioCallback = function(samplesRequested: kha.internal.IntBox, buffer: kha.audio2.Buffer): Void { + var samples = buffer.data; + var frames = Std.int(samples.length / 2); + for (i in 0...frames) { + if (sampleIndex < data.length) { + var input = data[sampleIndex++]; + var output: Float = 0.0; + + if (mode == "Low") { + lastOutput = lastOutput + alphaLow * (input - lastOutput); + output = lastOutput; + } else if (mode == "High") { + output = alphaHigh * (lastOutput + input - lastInput); + lastInput = input; + lastOutput = output; + } else if (mode == "Band") { + var hp = alphaHigh * (lastHigh + input - lastInput); + lastInput = input; + lastHigh = hp; + lastLow = lastLow + alphaBandLow * (hp - lastLow); + output = lastLow; + } + + if (output > 1.0) output = 1.0; + else if (output < -1.0) output = -1.0; + + samples[i * 2] = output; + samples[i * 2 + 1] = output; + } else { + samples[i * 2] = 0.0; + samples[i * 2 + 1] = 0.0; + } + } + }; + } + + public function playHaasDSP(delayMs: Float): Void { + if (sound == null || sound.uncompressedData == null) return; + + var delaySamples = Std.int((delayMs / 1000.0) * samplingRate); + if (delaySamples < 1) delaySamples = 1; + var delayBuffer = [for (i in 0...delaySamples) 0.0]; + var bufferIndex = 0; + var sampleIndex = 0; + var data = sound.uncompressedData; + + kha.audio2.Audio.audioCallback = function(samplesRequested: kha.internal.IntBox, buffer: kha.audio2.Buffer): Void { + var samples = buffer.data; + var frames = Std.int(samples.length / 2); + for (i in 0...frames) { + if (sampleIndex < data.length) { + var input = data[sampleIndex++]; + var delayedSample = delayBuffer[bufferIndex]; + delayBuffer[bufferIndex] = input; + bufferIndex = (bufferIndex + 1) % delaySamples; + + samples[i * 2] = input; + samples[i * 2 + 1] = delayedSample; + } else { + samples[i * 2] = 0.0; + samples[i * 2 + 1] = 0.0; + } + } + }; + } + + public function playHRTFPanner(camera: iron.object.CameraObject, soundPos: iron.math.Vec4): Void { + if (sound == null || sound.uncompressedData == null) return; + + var camTransform = camera.transform; + var relPos = soundPos.sub(camTransform.loc); + var rightVector = camTransform.right(); + var dotRight = relPos.x * rightVector.x + relPos.y * rightVector.y + relPos.z * rightVector.z; + var dist = relPos.length(); + var pan = dist > 0 ? dotRight / dist : 0.0; + + var leftGain = Math.min(1.0, 1.0 - pan); + var rightGain = Math.min(1.0, 1.0 + pan); + + var maxDelayMs = 0.75; + var maxDelaySamples = Std.int((maxDelayMs / 1000.0) * samplingRate); + if (maxDelaySamples < 1) maxDelaySamples = 1; + var delaySamples = Std.int(Math.abs(pan) * maxDelaySamples); + + var delayBuffer = [for (i in 0...maxDelaySamples + 1) 0.0]; + var bufferIndex = 0; + var sampleIndex = 0; + var data = sound.uncompressedData; + + kha.audio2.Audio.audioCallback = function(samplesRequested: kha.internal.IntBox, buffer: kha.audio2.Buffer): Void { + var samples = buffer.data; + var frames = Std.int(samples.length / 2); + for (i in 0...frames) { + if (sampleIndex < data.length) { + var input = data[sampleIndex++]; + + delayBuffer[bufferIndex] = input; + var delayedIndex = (bufferIndex - delaySamples + delayBuffer.length) % delayBuffer.length; + var delayedInput = delayBuffer[delayedIndex]; + bufferIndex = (bufferIndex + 1) % delayBuffer.length; + + var left = (pan > 0) ? delayedInput * leftGain : input * leftGain; + var right = (pan < 0) ? delayedInput * rightGain : input * rightGain; + + if (left > 1.0) left = 1.0; + else if (left < -1.0) left = -1.0; + if (right > 1.0) right = 1.0; + else if (right < -1.0) right = -1.0; + + samples[i * 2] = left; + samples[i * 2 + 1] = right; + } else { + samples[i * 2] = 0.0; + samples[i * 2 + 1] = 0.0; + } + } + }; + } +} diff --git a/leenkx/Sources/leenkx/trait/internal/Spectral.hx b/leenkx/Sources/leenkx/trait/internal/Spectral.hx new file mode 100644 index 00000000..0a13db07 --- /dev/null +++ b/leenkx/Sources/leenkx/trait/internal/Spectral.hx @@ -0,0 +1,101 @@ +//https://github.com/rvanwijnen/spectral.js 2023 Ronald van Wijnen. + +package leenkx.trait.internal; +import iron.math.Vec4; + +class Spectral { + static inline var SIZE = 38; + static inline var GAMMA = 2.4; + + static var W = [1.0011607, 1.0011606, 1.0011603, 1.0011586, 1.0011525, 1.0011325, 1.0010850, 1.0009968, 1.0008652, 1.0006962, 1.0005049, 1.0003080, 1.0001196, 0.9999527, 0.9998218, 0.9997386, 0.9997095, 0.9997319, 0.9997994, 0.9999003, 1.0000204, 1.0001447, 1.0002599, 1.0003557, 1.0004275, 1.0004762, 1.0005072, 1.0005251, 1.0005350, 1.0005402, 1.0005427, 1.0005438, 1.0005444, 1.0005447, 1.0005448, 1.0005449, 1.0005449, 1.0005449]; + static var C = [0.9705850, 0.9705924, 0.9706253, 0.9707868, 0.9713686, 0.9731632, 0.9767402, 0.9815876, 0.9862802, 0.9899491, 0.9924927, 0.9941456, 0.9951839, 0.9957567, 0.9959128, 0.9956061, 0.9945976, 0.9922157, 0.9862364, 0.9679433, 0.8912850, 0.5362024, 0.1541081, 0.0574575, 0.0315349, 0.0222633, 0.0182022, 0.0162990, 0.0153656, 0.0149111, 0.0146954, 0.0145964, 0.0145470, 0.0145228, 0.0145120, 0.0145066, 0.0145044, 0.0145038]; + static var M = [0.9906735, 0.9906715, 0.9906625, 0.9906181, 0.9904514, 0.9898710, 0.9882866, 0.9842906, 0.9739349, 0.9418178, 0.8173903, 0.4324728, 0.1384539, 0.0537347, 0.0292174, 0.0213136, 0.0201349, 0.0241323, 0.0372236, 0.0760506, 0.2053754, 0.5412689, 0.8158416, 0.9128177, 0.9463398, 0.9599276, 0.9662605, 0.9693259, 0.9708545, 0.9716050, 0.9719627, 0.9721272, 0.9722094, 0.9722495, 0.9722676, 0.9722765, 0.9722802, 0.9722813]; + static var Y = [0.0210523, 0.0210564, 0.0210746, 0.0211649, 0.0215027, 0.0226738, 0.0258235, 0.0334879, 0.0519069, 0.1007490, 0.2391298, 0.5348043, 0.7978075, 0.9114498, 0.9537979, 0.9712416, 0.9793031, 0.9833801, 0.9854612, 0.9864350, 0.9867382, 0.9866178, 0.9862777, 0.9858605, 0.9854749, 0.9851769, 0.9849715, 0.9848463, 0.9847753, 0.9847380, 0.9847196, 0.9847110, 0.9847066, 0.9847045, 0.9847035, 0.9847031, 0.9847029, 0.9847028]; + static var RR = [0.0315605, 0.0315520, 0.0315148, 0.0313318, 0.0306729, 0.0286480, 0.0246450, 0.0192960, 0.0142066, 0.0102942, 0.0076191, 0.0058980, 0.0048233, 0.0042298, 0.0040599, 0.0043533, 0.0053434, 0.0076917, 0.0135969, 0.0316975, 0.1078611, 0.4638126, 0.8470554, 0.9431854, 0.9688621, 0.9780306, 0.9820436, 0.9839236, 0.9848454, 0.9852942, 0.9855072, 0.9856050, 0.9856538, 0.9856776, 0.9856883, 0.9856936, 0.9856958, 0.9856965]; + static var G = [0.0095560, 0.0095581, 0.0095673, 0.0096129, 0.0097837, 0.0103786, 0.0120026, 0.0160977, 0.0267061, 0.0595555, 0.1860398, 0.5705798, 0.8614677, 0.9458790, 0.9704654, 0.9784136, 0.9795890, 0.9755335, 0.9622887, 0.9231215, 0.7934340, 0.4592701, 0.1855741, 0.0881774, 0.0543630, 0.0406288, 0.0342215, 0.0311185, 0.0295708, 0.0288108, 0.0284486, 0.0282820, 0.0281988, 0.0281581, 0.0281398, 0.0281308, 0.0281271, 0.0281260]; + static var B = [0.9794047, 0.9794007, 0.9793829, 0.9792943, 0.9789630, 0.9778144, 0.9747243, 0.9671984, 0.9490796, 0.9008501, 0.7631504, 0.4659221, 0.2012632, 0.0877524, 0.0457176, 0.0284706, 0.0205271, 0.0165302, 0.0145135, 0.0136003, 0.0133604, 0.0135488, 0.0139594, 0.0144434, 0.0148854, 0.0152254, 0.0154592, 0.0156018, 0.0156824, 0.0157248, 0.0157458, 0.0157556, 0.0157605, 0.0157629, 0.0157640, 0.0157645, 0.0157648, 0.0157648]; + + static var CMF_X = [0.0000646, 0.0002194, 0.0011205, 0.0037666, 0.0118805, 0.0232864, 0.0345594, 0.0372237, 0.0324183, 0.0212332, 0.0104909, 0.0032958, 0.0005070, 0.0009486, 0.0062737, 0.0168646, 0.0286896, 0.0426748, 0.0562547, 0.0694703, 0.0830531, 0.0861260, 0.0904661, 0.0850038, 0.0709066, 0.0506288, 0.0354739, 0.0214682, 0.0125164, 0.0068045, 0.0034645, 0.0014976, 0.0007697, 0.0004073, 0.0001690, 0.0000952, 0.0000490, 0.0000199]; + static var CMF_Y = [0.0000018, 0.0000062, 0.0000310, 0.0001047, 0.0003536, 0.0009514, 0.0022822, 0.0042073, 0.0066887, 0.0098883, 0.0152494, 0.0214183, 0.0334229, 0.0513100, 0.0704020, 0.0878387, 0.0942490, 0.0979566, 0.0941521, 0.0867810, 0.0788565, 0.0635267, 0.0537414, 0.0426460, 0.0316173, 0.0208852, 0.0138601, 0.0081026, 0.0046301, 0.0024913, 0.0012593, 0.0005416, 0.0002779, 0.0001471, 0.0000610, 0.0000343, 0.0000177, 0.0000072]; + static var CMF_Z = [0.0003050, 0.0010368, 0.0053131, 0.0179543, 0.0570775, 0.1136516, 0.1733587, 0.1962065, 0.1860823, 0.1399504, 0.0891745, 0.0478962, 0.0281456, 0.0161376, 0.0077591, 0.0042961, 0.0020055, 0.0008614, 0.0003690, 0.0001914, 0.0001495, 0.0000923, 0.0000681, 0.0000288, 0.0000157, 0.0000039, 0.0000015, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; + + static function uncompand(x: Float): Float { + return x > 0.04045 ? Math.pow((x + 0.055) / 1.055, GAMMA) : x / 12.92; + } + + static function compand(x: Float): Float { + return x > 0.0031308 ? 1.055 * Math.pow(x, 1.0 / GAMMA) - 0.055 : x * 12.92; + } + + static function lRGB_to_R(r: Float, g: Float, b: Float): Array { + var w = Math.min(r, Math.min(g, b)); + var lr = r - w; + var lg = g - w; + var lb = b - w; + var cc = Math.min(lg, lb); + var mm = Math.min(lr, lb); + var yy = Math.min(lr, lg); + var red = Math.max(0, Math.min(lr - lb, lr - lg)); + var green = Math.max(0, Math.min(lg - lb, lg - lr)); + var blue = Math.max(0, Math.min(lb - lg, lb - lr)); + var res = []; + for (i in 0...SIZE) { + res.push(Math.max(1e-7, w * W[i] + cc * C[i] + mm * M[i] + yy * Y[i] + red * RR[i] + green * G[i] + blue * B[i])); + } + return res; + } + + public static function mix(colors: Array, factors: Array): Vec4 { + var ksArrays: Array> = []; + var luminances: Array = []; + + for (c in colors) { + var lr = uncompand(c.x); + var lg = uncompand(c.y); + var lb = uncompand(c.z); + var R = lRGB_to_R(lr, lg, lb); + var ks = []; + for (val in R) ks.push(Math.pow(1.0 - val, 2) / (2.0 * val)); + ksArrays.push(ks); + var y = 0.0; + for (i in 0...SIZE) y += R[i] * CMF_Y[i]; + luminances.push(Math.max(1e-7, y)); + } + + var mixedR = []; + var finalAlpha = 0.0; + + for (i in 0...SIZE) { + var ksMix = 0.0; + var totalConc = 0.0; + var alphaAccum = 0.0; + for (j in 0...colors.length) { + var conc = Math.pow(factors[j], 2) * luminances[j]; + totalConc += conc; + ksMix += ksArrays[j][i] * conc; + if (i == 0) alphaAccum += colors[j].w * conc; + } + if (i == 0) finalAlpha = alphaAccum / totalConc; + var finalKs = ksMix / totalConc; + mixedR.push(1.0 + finalKs - Math.sqrt(Math.pow(finalKs, 2) + 2.0 * finalKs)); + } + + var X = 0.0, Y = 0.0, Z = 0.0; + for (i in 0...SIZE) { + X += mixedR[i] * CMF_X[i]; + Y += mixedR[i] * CMF_Y[i]; + Z += mixedR[i] * CMF_Z[i]; + } + + var outR = 3.240970 * X - 1.537383 * Y - 0.498611 * Z; + var outG = -0.969244 * X + 1.875968 * Y + 0.041555 * Z; + var outB = 0.055630 * X - 0.203977 * Y + 1.056972 * Z; + + return new Vec4( + compand(Math.max(0, Math.min(1, outR))), + compand(Math.max(0, Math.min(1, outG))), + compand(Math.max(0, Math.min(1, outB))), + finalAlpha + ); + } +} diff --git a/leenkx/Sources/leenkx/trait/internal/UniformsManager.hx b/leenkx/Sources/leenkx/trait/internal/UniformsManager.hx index 42865a72..268e92f0 100644 --- a/leenkx/Sources/leenkx/trait/internal/UniformsManager.hx +++ b/leenkx/Sources/leenkx/trait/internal/UniformsManager.hx @@ -112,17 +112,28 @@ class UniformsManager extends Trait{ case "float": var link = constant.link; var value = constant.floatValue; - setFloatValue(material, object, link, value); - register(Float); - + + var rootMaterialMap = floatsMap.get(object); + var entry = rootMaterialMap != null ? rootMaterialMap.get(material) : null; + + if (entry == null || entry.get(link) == null) { + setFloatValue(material, object, link, value); + register(Float); + } + case "vec3": var vec = new Vec4(); vec.x = constant.vec3Value.get(0); vec.y = constant.vec3Value.get(1); vec.z = constant.vec3Value.get(2); - setVec3Value(material, object, constant.link, vec); - register(Vector); + var rootMaterialMap = vectorsMap.get(object); + var entry = rootMaterialMap != null ? rootMaterialMap.get(material) : null; + + if (entry == null || entry.get(constant.link) == null) { + setVec3Value(material, object, constant.link, vec); + register(Vector); + } } } } @@ -131,17 +142,24 @@ class UniformsManager extends Trait{ uniformExist = true; var object = Scene.active.root; // Map default texture to scene root + + var rootMaterialMap = texturesMap.get(object); + var entry = rootMaterialMap != null ? rootMaterialMap.get(material) : null; + var link = texture.link; + + if (entry == null || entry.get(link) == null) { - if (texture.default_image_file == null) { - setTextureValue(material, object, texture.link, null); + if (texture.default_image_file == null) { + setTextureValue(material, object, link, null); + } + else { + iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { + setTextureValue(material, object, link, image); + }); + } + register(Texture); } - else { - iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { - setTextureValue(material, object, texture.link, image); - }); - } - register(Texture); } } } diff --git a/leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx index daf4fe2e..85e8ccdb 100644 --- a/leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx @@ -13,12 +13,21 @@ import leenkx.ui.Canvas; class DebugDrawHelper { final navigation: Navigation; final lines: Array = []; + final texts: Array = []; + + var font: kha.Font = null; var debugMode: Navigation.DebugDrawMode = NoDebug; public function new(navigation: Navigation) { this.navigation = navigation; + #if lnx_ui + iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont: kha.Font) { + font = defaultFont; + }); + #end + iron.App.notifyOnRender2D(onRender); } @@ -56,6 +65,19 @@ class DebugDrawHelper { for(navMesh in Navigation.active.navMeshes) { navMesh.drawDebugMesh(this); + + if (font != null) { + final screenPos = worldToScreenFast(new Vec4().setFrom(navMesh.object.transform.loc)); + + if (screenPos.w == 1) { + texts.push({ + x: screenPos.x, + y: screenPos.y, + color: kha.Color.White, + text: navMesh.navMeshId + }); + } + } } g.opacity = 1.0; @@ -65,6 +87,18 @@ class DebugDrawHelper { g.drawLine(line.fromX, line.fromY, line.toX, line.toY, 1.0); } lines.resize(0); + + if (font != null) { + g.font = font; + g.fontSize = 18; + + for (text in texts) { + g.color = text.color; + g.drawString(text.text, text.x, text.y); + } + + texts.resize(0); + } } /** @@ -99,4 +133,12 @@ class LineData { public var toY: FastFloat; public var color: kha.Color; } + +@:structInit +class TextData { + public var x: FastFloat; + public var y: FastFloat; + public var color: kha.Color; + public var text: String; +} #end diff --git a/leenkx/Sources/leenkx/trait/navigation/Navigation.hx b/leenkx/Sources/leenkx/trait/navigation/Navigation.hx index 0279de96..f3ec7d9e 100644 --- a/leenkx/Sources/leenkx/trait/navigation/Navigation.hx +++ b/leenkx/Sources/leenkx/trait/navigation/Navigation.hx @@ -9,8 +9,7 @@ class Navigation extends iron.Trait { public static var active: Navigation = null; - public var navMeshes: Array = []; - //public var recast: Recast; + public var navMeshes: Map = new Map(); public var debugDrawHelper: DebugDrawHelper = null; diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx index 1f7d0bc9..84653916 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx @@ -244,7 +244,7 @@ class DebugDrawHelper { if (font != null) { g.font = font; - g.fontSize = 12; + g.fontSize = 18; for (text in texts) { g.color = text.color; g.drawString(text.text, text.x, text.y); diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/KinematicCharacterController.hx b/leenkx/Sources/leenkx/trait/physics/bullet/KinematicCharacterController.hx index cfc1049e..cb535874 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/KinematicCharacterController.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/KinematicCharacterController.hx @@ -169,7 +169,7 @@ class KinematicCharacterController extends Trait { body.setUserIndex(id); #end - // physics.addKinematicCharacterController(this); + physics.addKinematicCharacterController(this); if (onReady != null) onReady(); } @@ -248,7 +248,7 @@ class KinematicCharacterController extends Trait { #end public function removeFromWorld() { - // if (physics != null) physics.removeKinematicCharacterController(this); + if (physics != null) physics.removeKinematicCharacterController(this); } public function activate() { diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx index 391fd10e..b207f0d6 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx @@ -1,7 +1,6 @@ package leenkx.trait.physics.bullet; import iron.math.Vec4; -import iron.Scene; import iron.object.Object; #if lnx_bullet import Math; @@ -339,7 +338,6 @@ class PhysicsConstraint extends iron.Trait { id = nextId; nextId++; - notifyOnRemove(removeFromWorld); } else this.remove(); // Rigid body not initialized yet. Remove trait without adding constraint diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx index a26c06c6..491121ca 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx @@ -48,10 +48,7 @@ class PhysicsHook extends Trait { this.targetName = targetName; this.verts = verts; - iron.Scene.active.notifyOnInit(function() { - notifyOnInit(init); - notifyOnUpdate(update); - }); + notifyOnInit(init); } function init() { @@ -117,6 +114,8 @@ class PhysicsHook extends Trait { } } } + notifyOnUpdate(update); + notifyOnRemove(removeFromWorld); return; } #end @@ -144,11 +143,11 @@ class PhysicsHook extends Trait { vec1.setZ(10); constraint.setAngularUpperLimit(vec1); physics.world.addConstraint(constraint, false); + notifyOnRemove(removeFromWorld); return; } - // Rigid body or soft body not initialized yet - notifyOnInit(init); + this.remove(); } function update() { @@ -182,6 +181,66 @@ class PhysicsHook extends Trait { // } } + public function removeFromWorld() { + var physics = PhysicsWorld.active; + if (physics == null) return; + + if (constraint != null) { + physics.world.removeConstraint(constraint); + constraint = null; + } + + //hack: SB loses collision after removing hook + #if lnx_physics_soft + var softBody = object.getTrait(SoftBody); + if (softBody != null && softBody.body != null) { + @:privateAccess + var oldNodes = softBody.body.get_m_nodes(); + var count = oldNodes.size(); + + var savedData = []; + for (i in 0...count) { + var pos = oldNodes.at(i).get_m_x(); + var nor = oldNodes.at(i).get_m_n(); + savedData.push({ + x: pos.x(), y: pos.y(), z: pos.z(), + nx: nor.x(), ny: nor.y(), nz: nor.z() + }); + } + + var newSoftBody = new SoftBody( + softBody.shape, + softBody.bend, + softBody.mass, + softBody.margin, + softBody.friction, + softBody.damping, + softBody.linearStiffness, + softBody.angularStiffness, + softBody.pressure + ); + + object.removeTrait(softBody); + object.addTrait(newSoftBody); + + @:privateAccess { + newSoftBody.init(); + newSoftBody._init = null; + + if (newSoftBody.body != null) { + var newNodes = newSoftBody.body.get_m_nodes(); + for (i in 0...count) { + var d = savedData[i]; + newNodes.at(i).get_m_x().setValue(d.x, d.y, d.z); + newNodes.at(i).get_m_n().setValue(d.nx, d.ny, d.nz); + } + newSoftBody.update(); + } + } + } + #end + } + } #end diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 739d3bb1..d1f389e4 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -228,27 +228,31 @@ class PhysicsWorld extends Trait { constraint.delete(); } - // public function addKinematicCharacterController(controller:KinematicCharacterController) { - // if (!pairCache){ // Only create PairCache if needed - // world.getPairCache().setInternalGhostPairCallback(BtGhostPairCallbackPointer.create()); - // pairCache = true; - // } - // world.addAction(controller.character); - // world.addCollisionObjectToGroup(controller.body, controller.group, controller.group); - // } + public function addKinematicCharacterController(controller:KinematicCharacterController) { + if (!pairCache){ // Only create PairCache if needed + #if js + world.getPairCache().setInternalGhostPairCallback(new bullet.Bt.GhostPairCallback()); + #elseif cpp + world.getPairCache().setInternalGhostPairCallback(untyped __cpp__("new btGhostPairCallback()")); + #end + pairCache = true; + } + world.addAction(controller.character); + world.addCollisionObjectToGroup(controller.body, controller.group, -1); + } - // public function removeKinematicCharacterController(controller:KinematicCharacterController) { - // if (world != null) { - // world.removeCollisionObject(controller.body); - // world.removeAction(controller.character); - // } - // #if js - // bullet.Bt.Ammo.destroy(controller.body); - // #else - // var cbody = controller.body; - // untyped __cpp__("delete cbody"); - // #end - // } + public function removeKinematicCharacterController(controller:KinematicCharacterController) { + if (world != null) { + world.removeCollisionObject(controller.body); + world.removeAction(controller.character); + } + #if js + bullet.Bt.Ammo.destroy(controller.body); + #else + var cbody = controller.body; + untyped __cpp__("delete cbody"); + #end + } /** Used to get intersecting rigid bodies with the passed in RigidBody as reference. Often used when checking for object collisions. diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx b/leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx index b50d20f0..9c2f6937 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx @@ -606,6 +606,11 @@ class RigidBody extends iron.Trait { body.setGravity(vec1); } + public function getGravity(): Vec4 { + var g = body.getGravity(); + return (new Vec4(g.x(), g.y(), g.z())); + } + public function enableGravity() { body.setGravity(physics.world.getGravity()); } @@ -714,10 +719,15 @@ class RigidBody extends iron.Trait { public function setFriction(f: Float) { var bodyColl: bullet.Bt.CollisionObject = body; bodyColl.setFriction(f); - // bodyColl.setRollingFriction(f); this.friction = f; } + public function setAngularFriction(f: Float) { + var bodyColl: bullet.Bt.CollisionObject = body; + bodyColl.setRollingFriction(f); + this.angularFriction = f; + } + public function notifyOnContact(f: RigidBody->Void) { if (onContact == null) onContact = []; onContact.push(f); diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx b/leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx index f2359f2c..affe01e3 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx @@ -27,10 +27,16 @@ class SoftBody extends Trait { static var physics: PhysicsWorld = null; public var ready = false; - var shape: SoftShape; - var bend: Float; - var mass: Float; - var margin: Float; + public var shape: SoftShape; + public var bend: Int; + public var mass: Float; + public var margin: Float; + + public var friction: Float; + public var damping: Float; + public var pressure: Float; + public var linearStiffness: Float; + public var angularStiffness: Float; public var vertOffsetX = 0.0; public var vertOffsetY = 0.0; @@ -44,12 +50,19 @@ class SoftBody extends Trait { var vertexIndexMap: Map>; - public function new(shape = SoftShape.Cloth, bend = 0.5, mass = 1.0, margin = 0.04) { - super(); + public function new(shape = SoftShape.Cloth, bend = 0.5, mass = 1.0, margin = 0.04, + friction = 0.5, damping = 0.01, lStiff = 0.9, aStiff = 0.9, pressure = 0.0) { + super(); + this.shape = shape; - this.bend = bend; + this.bend = Std.int(Math.min(Math.ceil(bend), 5)); this.mass = mass; this.margin = margin; + this.friction = friction; + this.damping = damping; + this.linearStiffness = lStiff; + this.angularStiffness = aStiff; + this.pressure = pressure; //notifyOnAdd(init); //The above line works as well, but the object transforms are not set @@ -203,31 +216,51 @@ class SoftBody extends Trait { floatArray.delete(); intArray.delete(); #end - // body.generateClusters(4); + + body.generateClusters(16); #if js var cfg = body.get_m_cfg(); cfg.set_viterations(physics.solverIterations); cfg.set_piterations(physics.solverIterations); - // cfg.set_collisions(0x0001 + 0x0020 + 0x0040); // self collision - cfg.set_collisions(0x11); // Soft-rigid, soft-soft - if (shape == SoftShape.Volume) { - cfg.set_kDF(0.1); - cfg.set_kDP(0.01); - cfg.set_kPR(bend); - } + cfg.set_collisions(0x0001 | 0x0010 | 0x0020 | 0x0040); // self collision + //cfg.set_collisions(0x11); // Soft-rigid, soft-soft + cfg.set_kDF(this.friction); + cfg.set_kDP(this.damping); + + if (shape == SoftShape.Volume) + cfg.set_kPR(this.pressure); + else + cfg.set_kPR(0.0); + + var mat = body.get_m_materials().at(0); + mat.set_m_kLST(this.linearStiffness); + mat.set_m_kAST(this.angularStiffness); + + if (this.bend > 0) + body.generateBendingConstraints(bend, mat); #else //Not passed as refernece var cfg = body.m_cfg; cfg.viterations = physics.solverIterations; cfg.piterations = physics.solverIterations; - // body.m_cfg.collisions = 0x0001 + 0x0020 + 0x0040; - cfg.collisions = 0x11; // Soft-rigid, soft-soft - if (shape == SoftShape.Volume) { - cfg.kDF = 0.1; - cfg.kDP = 0.01; - cfg.kPR = bend; - } + //body.m_cfg.collisions = 0x0001 + 0x0020 + 0x0040; + cfg.collisions = 0x0001 | 0x0010 | 0x0020 | 0x0040; + //cfg.collisions = 0x11; // Soft-rigid, soft-soft + cfg.kDF = this.friction; + cfg.kDP = this.damping; + + if (shape == SoftShape.Volume) + cfg.kPR = this.pressure; + else + cfg.kPR = 0.0; + + var mat = body.m_materials.at(0); + mat.m_kLST = this.linearStiffness; + mat.m_kAST = this.angularStiffness; + + if (this.bend > 0) + body.generateBendingConstraints(bend, mat); //Set config again in HL body.m_cfg = cfg; #end @@ -240,6 +273,7 @@ class SoftBody extends Trait { #if hl cfg.delete(); + mat.delete(); #end notifyOnRemove(removeFromWorld); diff --git a/leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx b/leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx index 66bf7d93..f25b3bbd 100644 --- a/leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx +++ b/leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx @@ -727,6 +727,10 @@ class RigidBody extends Trait { setGravityFactor(0.0); } + public function getGravity(): Vec4 { + return physics.getGravity(); + } + public function enableGravity() { setGravityFactor(1.0); } diff --git a/leenkx/Sources/leenkx/ui/Canvas.hx b/leenkx/Sources/leenkx/ui/Canvas.hx index 8059db81..cf056848 100644 --- a/leenkx/Sources/leenkx/ui/Canvas.hx +++ b/leenkx/Sources/leenkx/ui/Canvas.hx @@ -214,7 +214,14 @@ class Canvas { ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); - ui.slider(h.nest(element.id), getText(canvas, element), 0.0, 1.0, true, 100, true, element.alignment); + var v = getText(canvas, element).split(";"); + if (v.length == 1){ + v[1] = "0"; + v[2] = "1"; + v[3] = "100"; + } + + ui.slider(h.nest(element.id), v[0], Std.parseFloat(v[1]), Std.parseFloat(v[2]), true, Std.parseFloat(v[3]), true, element.alignment); ui.t.TEXT_COL = prevTEXT_COL; ui.t.LABEL_COL = prevLABEL_COL; @@ -465,6 +472,7 @@ typedef TElement = { @:optional var asset: String; @:optional var visible: Null; @:optional var editable: Null; + @:optional var slider_max: Null; } typedef TAsset = { diff --git a/leenkx/Sources/zui/Ext.hx b/leenkx/Sources/zui/Ext.hx index b9534502..7385961e 100644 --- a/leenkx/Sources/zui/Ext.hx +++ b/leenkx/Sources/zui/Ext.hx @@ -197,7 +197,7 @@ class Ext { ui._x = px - (scroll ? 0 : ui.SCROLL_W() / 2); ui._y = py; - ui.image(ui.ops.black_white_gradient); + ui.image(ui.ops.black_white_gradient, kha.Color.fromFloats(cval, cval, cval, 0.15)); ui.g.color = 0xff000000; ui.g.fillRect(cx - 3 * ui.SCALE(), cy - 3 * ui.SCALE(), 6 * ui.SCALE(), 6 * ui.SCALE()); diff --git a/leenkx/Sources/zui/Zui.hx b/leenkx/Sources/zui/Zui.hx index 76b4118a..c050e399 100644 --- a/leenkx/Sources/zui/Zui.hx +++ b/leenkx/Sources/zui/Zui.hx @@ -6,6 +6,7 @@ import kha.input.Surface; import kha.input.Keyboard; import kha.input.KeyCode; import kha.graphics2.Graphics; +using zui.GraphicsExtension; @:structInit typedef ZuiOptions = { @@ -996,10 +997,16 @@ class Zui { hover ? t.BUTTON_HOVER_COL : t.BUTTON_COL; - drawRect(g, t.FILL_BUTTON_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H()); - + var v = text.split(";"); + if (v.length == 2){ + var radius = Std.parseFloat(v[1]) * SCALE(); + text = v[0]; + drawRoundRect(g, t.FILL_BUTTON_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H(), radius); + } else + drawRect(g, t.FILL_BUTTON_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H()); + g.color = t.BUTTON_TEXT_COL; - drawString(g, text, null, 0, align); + drawString(g, text, null, -buttonOffsetY + SCALE(), align); if (label != "") { g.color = t.LABEL_COL; drawString(g, label, null, 0, align == Align.Right ? Align.Left : Align.Right); @@ -1717,6 +1724,24 @@ class Zui { fill ? g.fillRect(x, y - 1, w, h + 1) : g.drawRect(x, y, w, h, strength); } + public function drawRoundRect(g: Graphics, fill: Bool, x: Float, y: Float, w: Float, h: Float, r: Float) { + if (r <= 0) { + fill ? g.fillRect(x, y, w, h) : g.drawRect(x, y, w, h); + return; + } + + if (fill) { + g.fillRect(x + r, y, w - 2 * r, h); + g.fillRect(x, y + r, w, h - 2 * r); + g.fillCircle(x + r, y + r, r); + g.fillCircle(x + w - r, y + r, r); + g.fillCircle(x + r, y + h - r, r); + g.fillCircle(x + w - r, y + h - r, r); + } else { + g.drawRect(x, y, w, h); + } + } + function isVisible(elemH: Float): Bool { if (currentWindow == null) return true; return (_y + elemH > windowHeaderH && _y < currentWindow.texture.height); diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 043e9bf6..e3a39407 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -64,13 +64,14 @@ class NodeType(Enum): SPEAKER = 5 DECAL = 6 PROBE = 7 + CURVE = 8 @classmethod def get_bobject_type(cls, bobject: bpy.types.Object) -> "NodeType": """Returns the NodeType enum member belonging to the type of the given blender object.""" if bobject.type == "MESH": - if bobject.data.polygons or bobject.data.edges or bobject.data.vertices: + if bobject.data.polygons: #or bobject.data.edges or bobject.data.vertices: return cls.MESH elif bobject.type in ('FONT', 'META', 'CURVE'): # FIXME: curves with meshes shouldn't be used in modifiers for now. if bobject.type == 'CURVE': @@ -89,12 +90,14 @@ class NodeType(Enum): return cls.SPEAKER elif bobject.type == "LIGHT_PROBE": return cls.PROBE + elif bobject.type == "CURVE": + return cls.CURVE return cls.EMPTY STRUCT_IDENTIFIER = ("object", "bone_object", "mesh_object", "light_object", "camera_object", "speaker_object", - "decal_object", "probe_object") + "decal_object", "probe_object", "curve_object") # Internal target names for single FCurve data paths FCURVE_TARGET_NAMES = { @@ -161,6 +164,7 @@ class LeenkxExporter: self.bobject_array: Dict[bpy.types.Object, Dict[str, Union[NodeType, str]]] = {} self.bobject_bone_array = {} self.mesh_array = {} + self.curve_array = {} self.light_array = {} self.probe_array = {} self.camera_array = {} @@ -405,7 +409,7 @@ class LeenkxExporter: o['transform'] = {'values': LeenkxExporter.write_matrix(matrix_local)} # Animated transform - if bobject.animation_data is not None and bobject.type != "ARMATURE": + if bobject.animation_data is not None and bobject.type != "ARMATURE" and bobject.lnx_animation_enabled: action = bobject.animation_data.action if action is not None: @@ -987,7 +991,7 @@ class LeenkxExporter: out_object = self.object_to_lnx_object_dict[bobject] out_object['type'] = STRUCT_IDENTIFIER[object_type.value] - out_object['name'] = bobject_ref["structName"] + out_object['name'] = lnx.utils.safestr(bobject_ref["structName"]) if bobject.parent_type == "BONE": out_object['parent_bone'] = bobject.parent_bone @@ -1064,7 +1068,7 @@ class LeenkxExporter: _num_verts = len(_verts) _all_co = np.empty(_num_verts * 3, dtype=' 0: out_object['properties'] = [] for proplist_item in bobject.lnx_propertylist: - # Check if the property is a collection (array type). if proplist_item.type_prop == 'array': - # Convert the collection to a list. - array_type = proplist_item.array_item_type collection_value = getattr(proplist_item, 'array_prop') property_name = array_type + '_prop' - value = [str(getattr(item, property_name)) for item in collection_value] + + if array_type == 'vector': + value = [] + for item in collection_value: + v = getattr(item, property_name) + value.append([float(v[0]), float(v[1]), float(v[2])]) + else: + value = [getattr(item, property_name) for item in collection_value] + + elif proplist_item.type_prop == 'vector': + v = getattr(proplist_item, 'vector_prop') + value = [float(v[0]), float(v[1]), float(v[2])] + else: - # Handle other types of properties. value = getattr(proplist_item, proplist_item.type_prop + '_prop') out_property = { @@ -1163,6 +1175,9 @@ class LeenkxExporter: # Decal flag if mat is not None and mat.lnx_decal: out_object['type'] = 'decal_object' + if mat is not None: + if 'Rot' in bobject.lnx_instanced: + mat.lnx_instanced_rot_type = bobject.lnx_instanced_rot_type # No material, mimic cycles and assign default if len(out_object['material_refs']) == 0: self.use_default_material(bobject, out_object) @@ -1228,15 +1243,32 @@ class LeenkxExporter: self.speaker_array[objref]["objectTable"].append(bobject) out_object['data_ref'] = self.speaker_array[objref]["structName"] + elif object_type is NodeType.CURVE: + if objref not in self.curve_array: + self.curve_array[objref] = {"structName" : objname, "objectTable" : [bobject]} + else: + self.curve_array[objref]["objectTable"].append(bobject) + out_object['data_ref'] = lnx.utils.safestr(self.curve_array[objref]["structName"]) + + out_object['material_refs'] = [] + for i in range(len(bobject.material_slots)): + mat = self.slot_to_material(bobject, bobject.material_slots[i]) + # Export ref + self.export_material_ref(bobject, mat, i, out_object) + # Decal flag + if mat is not None and mat.lnx_decal: + out_object['type'] = 'decal_object' + + # Export the transform. If object is animated, then animation tracks are exported here if bobject.type != 'ARMATURE' and bobject.animation_data is not None: action = bobject.animation_data.action export_actions = [action] for track in bobject.animation_data.nla_tracks: - if track.strips is None: + if track.strips is None or track.mute: continue for strip in track.strips: - if strip.action is None or strip.action in export_actions: + if strip.action is None or strip.action in export_actions or strip.mute: continue export_actions.append(strip.action) orig_action = action @@ -1265,7 +1297,7 @@ class LeenkxExporter: bone_translation_pose = pose_bone.tail - pose_bone.head out_object['parent_bone_tail_pose'] = [bone_translation_pose[0], bone_translation_pose[1], bone_translation_pose[2]] - if bobject.type == 'ARMATURE' and bobject.data is not None: + if lnx.utils.get_rp().lnx_skin != 'Off' and bobject.type == 'ARMATURE' and bobject.data is not None: # Armature data bdata = bobject.data # Reference start action @@ -1290,10 +1322,10 @@ class LeenkxExporter: # armature object to deform with if hasattr(adata, 'nla_tracks') and adata.nla_tracks is not None: for track in adata.nla_tracks: - if track.strips is None: + if track.strips is None or track.mute: continue for strip in track.strips: - if strip.action is None: + if strip.action is None or strip.mute: continue if strip.action.name == action.name: continue @@ -2196,6 +2228,86 @@ class LeenkxExporter: except ReferenceError: pass + def export_curve(self, object_ref): + """Exports a single curve object.""" + table = object_ref[1]["objectTable"] + bobject = table[0] + curve_id = lnx.utils.safestr(object_ref[1]["structName"]) + + world = bpy.data.worlds['Lnx'] + if world.lnx_verbose_output: + print('Exporting curve ' + lnx.utils.asset_name(bobject.data)) + + curve_data = bobject.data + + out_curve = { + 'name': curve_id, + 'object': bobject.name, + 'splines': [], + 'strength': curve_data.lnx_draw_strength, + 'color': [curve_data.lnx_draw_color[0], curve_data.lnx_draw_color[1], curve_data.lnx_draw_color[2], curve_data.lnx_draw_color[3]] + } + + for spline in curve_data.splines: + if spline.type == 'BEZIER': + current_spline = { + 'closed': spline.use_cyclic_u, + 'resolution': spline.resolution_u, + 'points': [], + 'material_index': spline.material_index, + } + + for bezier_point in spline.bezier_points: + point_data = { + 'co': [bezier_point.co.x, bezier_point.co.y, bezier_point.co.z], + 'handle_left': [bezier_point.handle_left.x, bezier_point.handle_left.y, bezier_point.handle_left.z], + 'handle_right': [bezier_point.handle_right.x, bezier_point.handle_right.y, bezier_point.handle_right.z] + } + current_spline['points'].append(point_data) + + out_curve['splines'].append(current_spline) + + active_keys = [kb for kb in bobject.data.shape_keys.key_blocks if not kb.mute] if bobject.data.shape_keys else [] + + if len(active_keys) == 0: + temp_obj = bobject.to_mesh() + + if len(temp_obj.polygons) > 0: + temp_ref = [object_ref[0], { + "objectTable": table, + "structName": lnx.utils.safestr(curve_id) + }] + + self.export_mesh(temp_ref) + + bobject.to_mesh_clear() + else: + out_curve['shape_keys'] = [] + for kb in bobject.data.shape_keys.key_blocks: + key_data = { + 'name': kb.name, + 'value': kb.value, + 'points': [] + } + for p in kb.data: + key_data['points'].append({ + 'co': p.co[:], + 'handle_left': p.handle_left[:], + 'handle_right': p.handle_right[:] + }) + out_curve['shape_keys'].append(key_data) + + mat_count = len(bobject.material_slots) + if mat_count > 0: + out_curve['material_refs'] = [] + for i in range(mat_count): + mat = self.slot_to_material(bobject, bobject.material_slots[i]) + self.export_material_ref(bobject, mat, i, out_curve) + + self.output['curve_datas'].append(out_curve) + + + def export_light(self, object_ref): """Exports a single light object.""" rpdat = lnx.utils.get_rp() @@ -2288,11 +2400,11 @@ class LeenkxExporter: o = {'name': objectRef[1]["structName"]} bo = objectRef[0] - if bo.type == 'GRID': + if bo.type in ('GRID', 'VOLUME'): o['type'] = 'grid' - elif bo.type == 'PLANAR': + elif bo.type in ('PLANAR', 'PLANE'): o['type'] = 'planar' - else: + else: # (CUBEMAP, SPHERE) o['type'] = 'cubemap' self.output['probe_datas'].append(o) @@ -2348,6 +2460,7 @@ class LeenkxExporter: if bobject.type == 'CAMERA': self.output['camera_ref'] = asset_name self.has_spawning_camera = True + asset_name = bobject.name out_collection['object_refs'].append(asset_name) @@ -2731,7 +2844,7 @@ class LeenkxExporter: 'lifetime_random': psettings.lifetime_random, 'emit_from': emit_from, # Velocity - # 'normal_factor': psettings.normal_factor, + 'normal_factor': psettings.normal_factor, # 'tangent_factor': psettings.tangent_factor, # 'tangent_phase': psettings.tangent_phase, 'object_align_factor': ( @@ -2815,20 +2928,19 @@ class LeenkxExporter: def export_world(self): """Exports the world of the current scene.""" - world = self.scene.world + for world in bpy.data.worlds: + if world is not None: + world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name) - if world is not None: - world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name) + if world_name not in self.world_array: + self.world_array.append(world_name) + out_world = {'name': world_name} - if world_name not in self.world_array: - self.world_array.append(world_name) - out_world = {'name': world_name} + self.post_export_world(world, out_world) + self.output['world_datas'].append(out_world) - self.post_export_world(world, out_world) - self.output['world_datas'].append(out_world) - - elif lnx.utils.get_rp().rp_background == 'World': - log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared') + elif lnx.utils.get_rp().rp_background == 'World': + log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared') def export_objects(self, scene): """Exports all supported blender objects. @@ -2847,6 +2959,7 @@ class LeenkxExporter: self.output['light_datas'] = [] self.output['camera_datas'] = [] self.output['speaker_datas'] = [] + self.output['curve_datas'] = [] for light_ref in self.light_array.items(): self.export_light(light_ref) @@ -2867,6 +2980,9 @@ class LeenkxExporter: for lightprobe_object in self.probe_array.items(): self.export_probe(lightprobe_object) + for curve_ref in self.curve_array.items(): + self.export_curve(curve_ref) + self.output['mesh_datas'] = [] for mesh_ref in self.mesh_array.items(): self.export_mesh(mesh_ref) @@ -2896,9 +3012,9 @@ class LeenkxExporter: self.process_bobject(bobject) # Softbody needs connected triangles, use optimized # geometry export - for mod in bobject.modifiers: + """for mod in bobject.modifiers: if mod.type in ('CLOTH', 'SOFT_BODY'): - LeenkxExporter.optimize_enabled = True + LeenkxExporter.optimize_enabled = True""" self.process_skinned_meshes() @@ -3029,6 +3145,8 @@ class LeenkxExporter: self.export_scene_traits() + self.export_scene_properties() + self.export_canvas_themes() # Write embedded data references @@ -3145,6 +3263,14 @@ class LeenkxExporter: instanced_type = 4 instanced_data = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0] + attr_data_block = [] + for attr in bobject.lnx_instanced_attrs: + attr_data_block.extend([0.0] * attr.size) + instanced_data.extend(attr_data_block) + + if lnx.utils.export_morph_targets(bobject): + instanced_data.extend([0.0, 0.0, 0.0, 0.0]) + for child in bobject.children: if not child.lnx_export or child.hide_render: continue @@ -3163,6 +3289,9 @@ class LeenkxExporter: instanced_data.append(scale.x) instanced_data.append(scale.y) instanced_data.append(scale.z) + instanced_data.extend(attr_data_block) + if lnx.utils.export_morph_targets(child): + instanced_data.extend([0.0, 0.0, 0.0, 0.0]) break # Instance render collections with same children? @@ -3338,9 +3467,9 @@ class LeenkxExporter: # Phys traits if phys_enabled: for modifier in bobject.modifiers: - if modifier.type in ('CLOTH', 'SOFT_BODY'): + if modifier.type in ('CLOTH', 'SOFT_BODY') and modifier.show_render: self.add_softbody_mod(o, bobject, modifier) - elif modifier.type == 'HOOK': + elif modifier.type == 'HOOK' and modifier.show_render: self.add_hook_mod(o, bobject, modifier.object.name, modifier.vertex_group) # Rigid body constraint @@ -3392,7 +3521,7 @@ class LeenkxExporter: if bone: out_constraint['bone'] = bobject.name if hasattr(constraint, 'target') and constraint.target is not None: - if constraint.type == 'COPY_LOCATION': + if constraint.type in ('COPY_LOCATION', 'COPY_ROTATION'): out_constraint['target'] = constraint.target.name out_constraint['use_x'] = constraint.use_x out_constraint['use_y'] = constraint.use_y @@ -3402,9 +3531,38 @@ class LeenkxExporter: out_constraint['invert_z'] = constraint.invert_z out_constraint['use_offset'] = constraint.use_offset out_constraint['influence'] = constraint.influence + elif constraint.type in ('COPY_SCALE'): + out_constraint['target'] = constraint.target.name + out_constraint['use_x'] = constraint.use_x + out_constraint['use_y'] = constraint.use_y + out_constraint['use_z'] = constraint.use_z + out_constraint['use_offset'] = constraint.use_offset + out_constraint['influence'] = constraint.influence + elif constraint.type == 'COPY_TRANSFORMS': + out_constraint['target'] = constraint.target.name + out_constraint['influence'] = constraint.influence elif constraint.type == 'CHILD_OF': out_constraint['target'] = constraint.target.name out_constraint['influence'] = constraint.influence + elif constraint.type in ('LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE'): + if constraint.type == 'LIMIT_ROTATION': + out_constraint['use_limit_x'] = constraint.use_limit_x + out_constraint['use_limit_y'] = constraint.use_limit_y + out_constraint['use_limit_z'] = constraint.use_limit_z + else: + out_constraint['use_min_x'] = constraint.use_min_x + out_constraint['use_min_y'] = constraint.use_min_y + out_constraint['use_min_z'] = constraint.use_min_z + out_constraint['use_max_x'] = constraint.use_max_x + out_constraint['use_max_y'] = constraint.use_max_y + out_constraint['use_max_z'] = constraint.use_max_z + out_constraint['min_x'] = constraint.min_x + out_constraint['min_y'] = constraint.min_y + out_constraint['min_z'] = constraint.min_z + out_constraint['max_x'] = constraint.max_x + out_constraint['max_y'] = constraint.max_y + out_constraint['max_z'] = constraint.max_z + out_constraint['influence'] = constraint.influence o['constraints'].append(out_constraint) @@ -3632,6 +3790,35 @@ class LeenkxExporter: for out_trait in self.output['traits']: LeenkxExporter.import_traits.append(out_trait['class_name']) + def export_scene_properties(self) -> None: + bscene = self.scene + if len(bscene.lnx_propertylist) > 0: + if 'properties' not in self.output: + self.output['properties'] = [] + + for proplist_item in bscene.lnx_propertylist: + if proplist_item.type_prop == 'array': + array_type = proplist_item.array_item_type + collection_value = getattr(proplist_item, 'array_prop') + property_name = array_type + '_prop' + + if array_type == 'vector': + value = [[v for v in getattr(item, property_name)] for item in collection_value] + else: + value = [getattr(item, property_name) for item in collection_value] + + elif proplist_item.type_prop == 'vector': + value = [v for v in getattr(proplist_item, 'vector_prop')] + + else: + value = getattr(proplist_item, proplist_item.type_prop + '_prop') + + out_property = { + 'name': proplist_item.name_prop, + 'value': value + } + self.output['properties'].append(out_property) + @staticmethod def export_canvas_themes(): path_themes = os.path.join(lnx.utils.get_fp(), 'Bundled', 'canvas') @@ -3657,16 +3844,21 @@ class LeenkxExporter: # ClothModifier if modifier.type == 'CLOTH': bend = modifier.settings.bending_stiffness + friction = bobject.lnx_soft_body_friction + damping = bobject.lnx_soft_body_damping soft_type = 0 # SoftBodyModifier elif modifier.type == 'SOFT_BODY': - bend = (modifier.settings.bend + 1.0) * 10 + bend = modifier.settings.bend + friction = modifier.settings.friction + damping = modifier.settings.goal_friction soft_type = 1 else: # Wrong modifier type return - out_trait['parameters'] = [str(soft_type), str(bend), str(modifier.settings.mass), str(bobject.lnx_soft_body_margin)] + out_trait['parameters'] = [str(soft_type), str(bend), str(modifier.settings.mass), str(bobject.lnx_soft_body_margin), str(friction), str(damping), + str(bobject.lnx_soft_body_linear_stiffness), str(bobject.lnx_soft_body_angular_stiffness), str(bobject.lnx_soft_body_pressure)] o['traits'].append(out_trait) if soft_type == 0: diff --git a/leenkx/blender/lnx/exporter_opt.py b/leenkx/blender/lnx/exporter_opt.py index 98276a42..7a65f03c 100644 --- a/leenkx/blender/lnx/exporter_opt.py +++ b/leenkx/blender/lnx/exporter_opt.py @@ -214,7 +214,7 @@ def export_mesh_data(self, export_mesh: bpy.types.Mesh, bobject: bpy.types.Objec # Scale for packed coords maxdim = max(bobject.data.lnx_aabb[0], max(bobject.data.lnx_aabb[1], bobject.data.lnx_aabb[2])) if maxdim > 2: - o['scale_pos'] = maxdim / 2 + o['scale_pos'] = maxdim / 1 if bpy.app.version >= (4, 1, 0) else 2 else: o['scale_pos'] = 1.0 if has_armature: # Allow up to 2x bigger bounds for skinned mesh diff --git a/leenkx/blender/lnx/handlers.py b/leenkx/blender/lnx/handlers.py index 03b8f643..09f28bd4 100644 --- a/leenkx/blender/lnx/handlers.py +++ b/leenkx/blender/lnx/handlers.py @@ -228,7 +228,7 @@ context_screen = None @persistent def on_save_pre(context): # Ensure that files are saved with the correct version number - # (e.g. startup files with an "Arm" world may have old version numbers) + # (e.g. startup files with an "Lnx" world may have old version numbers) wrd = bpy.data.worlds['Lnx'] wrd.lnx_version = props.lnx_version wrd.lnx_commit = props.lnx_commit diff --git a/leenkx/blender/lnx/keymap.py b/leenkx/blender/lnx/keymap.py index 38c8b192..4393a6a6 100644 --- a/leenkx/blender/lnx/keymap.py +++ b/leenkx/blender/lnx/keymap.py @@ -29,8 +29,9 @@ def register(): if not lnx_start: kmw = keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") kmw.keymap_items.new(props_ui.LeenkxPlayButton.bl_idname, type='F5', value='PRESS') - kmw.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS') - kmw.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS') + kmw.keymap_items.new(props_ui.LeenkxCleanProjectButton.bl_idname, type='F6', value='PRESS') + #kmw.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS') + #kmw.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS') kmn = keyconfig.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') kmn.keymap_items.new('lnx.add_call_group_node', 'G', 'PRESS', shift=True) kmn.keymap_items.new('lnx.add_group_tree_from_selected', 'G', 'PRESS', ctrl=True) @@ -44,8 +45,9 @@ def register(): def unregister(): kmw = bpy.context.window_manager.keyconfigs.user.keymaps.get('Window') kmw.keymap_items.remove(kmw.keymap_items[props_ui.LeenkxPlayButton.bl_idname]) - kmw.keymap_items.remove(kmw.keymap_items['tlm.build_lightmaps']) - kmw.keymap_items.remove(kmw.keymap_items['tlm.clean_lightmaps']) + kmw.keymap_items.remove(kmw.keymap_items[props_ui.LeenkxCleanProjectButton.bl_idname]) + #kmw.keymap_items.remove(kmw.keymap_items['tlm.build_lightmaps']) + #kmw.keymap_items.remove(kmw.keymap_items['tlm.clean_lightmaps']) kmn = bpy.context.window_manager.keyconfigs.user.keymaps.get('Node Editor') kmn.keymap_items.remove(kmn.keymap_items['lnx.add_call_group_node']) kmn.keymap_items.remove(kmn.keymap_items['lnx.add_group_tree_from_selected']) diff --git a/leenkx/blender/lnx/lib/make_datas.py b/leenkx/blender/lnx/lib/make_datas.py index 5daa1f56..0026ae6d 100644 --- a/leenkx/blender/lnx/lib/make_datas.py +++ b/leenkx/blender/lnx/lib/make_datas.py @@ -1,3 +1,5 @@ +import os +import bpy from typing import List, Dict, Optional, Any import lnx.utils @@ -11,6 +13,14 @@ def parse_context( vert: Optional[List[str]] = None, frag: Optional[List[str]] = None, ): + if c["name"] == "compositor_pass": + wrd = bpy.data.worlds['Lnx'] + rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index] + if rpdat.lnx_custom_compositor: + custom_path = lnx.utils.get_fp().replace("\\", "/") + "/Compositor" + c["vertex_shader"] = custom_path + "/compositor_pass.vert.glsl" + c["fragment_shader"] = custom_path + "/compositor_pass.frag.glsl" + con = { "name": c["name"], "constants": [], @@ -233,6 +243,8 @@ def parse_shader( const = {"type": ctype, "name": cid} con["constants"].append(const) + #print(f"file: {c.get('vertex_shader', 'Unknown')} | Constant: {cid} ({ctype})") + check_link(c, defs, cid, const) diff --git a/leenkx/blender/lnx/logicnode/__init__.py b/leenkx/blender/lnx/logicnode/__init__.py index 84dfab6b..88f9fd67 100644 --- a/leenkx/blender/lnx/logicnode/__init__.py +++ b/leenkx/blender/lnx/logicnode/__init__.py @@ -43,6 +43,7 @@ def init_categories(): lnx_nodes.add_category('Light', icon='LIGHT', section="data") lnx_nodes.add_category('World', icon='WORLD', section="data") lnx_nodes.add_category('Object', icon='OBJECT_DATA', section="data") + lnx_nodes.add_category('Curve', icon='IPO_EASE_IN_OUT', section="data") lnx_nodes.add_category('Scene', icon='SCENE_DATA', section="data") lnx_nodes.add_category('Trait', icon='NODETREE', section="data") lnx_nodes.add_category('Network', icon='WORLD', section="data") diff --git a/leenkx/blender/lnx/logicnode/animation/LN_get_actions.py b/leenkx/blender/lnx/logicnode/animation/LN_get_actions.py new file mode 100644 index 00000000..b445c4e3 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/animation/LN_get_actions.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class AnimationNode(LnxLogicTreeNode): + """Returns the actions list of the given object.""" + bl_idname = 'LNAnimationNode' + bl_label = 'Get Actions' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + + self.add_output('LnxNodeSocketArray', 'Actions', is_var=False) + self.add_output('LnxIntSocket', 'Length') diff --git a/leenkx/blender/lnx/logicnode/array/LN_async_array_loop_node.py b/leenkx/blender/lnx/logicnode/array/LN_async_array_loop_node.py new file mode 100644 index 00000000..a8f437e0 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/array/LN_async_array_loop_node.py @@ -0,0 +1,18 @@ +from lnx.logicnode.lnx_nodes import * + + +class AsyncArrayLoopNode(LnxLogicTreeNode): + """Loops through each item of the given array.""" + bl_idname = 'LNAsyncArrayLoopNode' + bl_label = 'Async Array Loop' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketArray', 'Array') + self.add_input('LnxIntSocket', 'Async Items') + + self.add_output('LnxNodeSocketAction', 'Loop') + self.add_output('LnxDynamicSocket', 'Value') + self.add_output('LnxIntSocket', 'Index') + self.add_output('LnxNodeSocketAction', 'Done') diff --git a/leenkx/blender/lnx/logicnode/camera/LN_set_camera_projection.py b/leenkx/blender/lnx/logicnode/camera/LN_set_camera_projection.py new file mode 100644 index 00000000..449f3ce5 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/camera/LN_set_camera_projection.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCameraProjectionNode(LnxLogicTreeNode): + """Sets the projection of the given camera.""" + bl_idname = 'LNSetCameraProjectionNode' + bl_label = 'Set Camera Projection' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Camera') + self.add_input('LnxIntSocket', 'Width') + self.add_input('LnxIntSocket', 'Height') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/camera/LN_set_camera_start_end.py b/leenkx/blender/lnx/logicnode/camera/LN_set_camera_start_end.py index 1685716a..f612ea69 100644 --- a/leenkx/blender/lnx/logicnode/camera/LN_set_camera_start_end.py +++ b/leenkx/blender/lnx/logicnode/camera/LN_set_camera_start_end.py @@ -13,9 +13,9 @@ class SetCameraStartEndNode(LnxLogicTreeNode): self.inputs.remove(self.inputs[-1]) if self.property0 == 'Start': self.add_input('LnxFloatSocket', 'Start') - if self.property0 == 'End': + elif self.property0 == 'End': self.add_input('LnxFloatSocket', 'End') - if self.property0 == 'Start&End': + elif self.property0 == 'Start&End': self.add_input('LnxFloatSocket', 'Start') self.add_input('LnxFloatSocket', 'End') diff --git a/leenkx/blender/lnx/logicnode/camera/LN_set_screen_cameras.py b/leenkx/blender/lnx/logicnode/camera/LN_set_screen_cameras.py new file mode 100644 index 00000000..c5143d55 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/camera/LN_set_screen_cameras.py @@ -0,0 +1,60 @@ +from lnx.logicnode.lnx_nodes import * + +class SetScreenCamerasNode(LnxLogicTreeNode): + """Renders the scene from the view of specified cameras. + + @input Start: Evaluate the inputs and start drawing the camera render targets. + @input Stop: Stops the rendering and drawing of the camera render targets. + @input Camera: The camera from which to render. + @input X/Y: Position where the camera's render target is drawn, in pixels from the top left corner. + @input Width/Height: Size of the camera's render target in pixels. + + @output On Start: Activated after the `Start` input has been activated. + @output On Stop: Activated after the `Stop` input has been activated. + """ + bl_idname = 'LNSetScreenCamerasNode' + bl_label = 'Set Screen Cameras' + lnx_version = 1 + min_inputs = 7 + + num_choices: IntProperty(default=0, min=0) + + def __init__(self, *args, **kwargs): + super(SetScreenCamerasNode, self).__init__(*args, **kwargs) + array_nodes[str(id(self))] = self + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'Start') + self.add_input('LnxNodeSocketAction', 'Stop') + self.add_sockets() + + self.add_output('LnxNodeSocketAction', 'On Start') + self.add_output('LnxNodeSocketAction', 'On Stop') + + def add_sockets(self): + self.num_choices += 1 + self.add_input('LnxNodeSocketObject', 'Camera ' + str(self.num_choices)) + self.add_input('LnxIntSocket', 'X') + self.add_input('LnxIntSocket', 'Y') + self.add_input('LnxIntSocket', 'Width') + self.add_input('LnxIntSocket', 'Height') + + def remove_sockets(self): + if self.num_choices > 1: + for _ in range(5): + self.inputs.remove(self.inputs.values()[-1]) + self.num_choices -= 1 + + def draw_buttons(self, context, layout): + row = layout.row(align=True) + + op = row.operator('lnx.node_call_func', text='Add Camera', icon='PLUS', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'add_sockets' + + column = row.column(align=True) + op = column.operator('lnx.node_call_func', text='', icon='X', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'remove_sockets' + if len(self.inputs) == self.min_inputs: + column.enabled = False diff --git a/leenkx/blender/lnx/logicnode/curve/LN_along_curve.py b/leenkx/blender/lnx/logicnode/curve/LN_along_curve.py new file mode 100644 index 00000000..4f7117df --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_along_curve.py @@ -0,0 +1,17 @@ +from lnx.logicnode.lnx_nodes import * + +class AlongCurveNode(LnxLogicTreeNode): + """Sets an object along a curve.""" + bl_idname = 'LNAlongCurveNode' + bl_label = 'Along Curve' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Spline Index') + self.add_input('LnxStringSocket', 'Forward Axis', default_value = 'X') + self.add_input('LnxFloatSocket', 'Position') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_deform_curve.py b/leenkx/blender/lnx/logicnode/curve/LN_deform_curve.py new file mode 100644 index 00000000..cf5a9218 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_deform_curve.py @@ -0,0 +1,18 @@ +from lnx.logicnode.lnx_nodes import * + +class DeformCurveNode(LnxLogicTreeNode): + """Sets an object to deform using a curve.""" + bl_idname = 'LNDeformCurveNode' + bl_label = 'Deform Curve' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Spline Index') + self.add_input('LnxStringSocket', 'Forward Axis', default_value = 'X') + self.add_input('LnxFloatSocket', 'Start') + self.add_input('LnxFloatSocket', 'End', default_value = 1.0) + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_follow_curve.py b/leenkx/blender/lnx/logicnode/curve/LN_follow_curve.py new file mode 100644 index 00000000..6852812f --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_follow_curve.py @@ -0,0 +1,22 @@ +from lnx.logicnode.lnx_nodes import * + +class FollowCurveNode(LnxLogicTreeNode): + """Sets an object to follow a curve.""" + bl_idname = 'LNFollowCurveNode' + bl_label = 'Follow Curve' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Spline Index') + self.add_input('LnxStringSocket', 'Forward Axis', default_value = 'X') + self.add_input('LnxFloatSocket', 'Speed') + self.add_input('LnxBoolSocket', 'Forward', default_value = True) + self.add_input('LnxBoolSocket', 'Cyclic') + self.add_input('LnxFloatSocket', 'Start') + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketAction', 'Cycle') + self.add_output('LnxFloatSocket', 'Progress') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_get_curve_data.py b/leenkx/blender/lnx/logicnode/curve/LN_get_curve_data.py new file mode 100644 index 00000000..c136fa28 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_get_curve_data.py @@ -0,0 +1,18 @@ +from lnx.logicnode.lnx_nodes import * + +class GetCurveDataNode(LnxLogicTreeNode): + """Gets curve data.""" + bl_idname = 'LNGetCurveDataNode' + bl_label = 'Get Curve Data' + lnx_section = 'get' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Curve') + + self.add_output('LnxIntSocket', 'Splines Length') + self.add_output('LnxIntSocket', 'Equidistant Samples') + self.add_output('LnxBoolSocket', 'Draw') + self.add_output('LnxFloatSocket', 'Strength') + self.add_output('LnxColorSocket', 'Color') + self.add_output('LnxNodeSocketObject', 'Curve Mesh') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_get_curve_spline.py b/leenkx/blender/lnx/logicnode/curve/LN_get_curve_spline.py new file mode 100644 index 00000000..2d789cc3 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_get_curve_spline.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + +class GetCurveSplineNode(LnxLogicTreeNode): + """Gets curve spline.""" + bl_idname = 'LNGetCurveSplineNode' + bl_label = 'Get Curve Spline' + lnx_section = 'get' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Spline Index') + + self.add_output('LnxNodeSocketArray', 'Points') + self.add_output('LnxBoolSocket', 'Closed') + self.add_output('LnxIntSocket', 'Resolution') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_set_curve_data.py b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_data.py new file mode 100644 index 00000000..47116601 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_data.py @@ -0,0 +1,38 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCurveDataNode(LnxLogicTreeNode): + """Sets curve data.""" + bl_idname = 'LNSetCurveDataNode' + bl_label = 'Set Curve Data' + lnx_section = 'set' + lnx_version = 1 + + def update_sockets(self, context): + while len(self.inputs) > 2: + self.inputs.remove(self.inputs[-1]) + if self.property0 == 'Equidistant Samples': + self.add_input('LnxIntSocket', 'Equidistant Samples') + elif self.property0 == 'Strength': + self.add_input('LnxFloatSocket', 'Strength') + elif self.property0 == 'Color': + self.add_input('LnxColorSocket', 'Color') + else: + self.add_input('LnxIntSocket', 'Resolution', default_value = 12) + + property0: HaxeEnumProperty( + 'property0', + items = [('Equidistant Samples', 'Equidistant Samples', 'Equidistant Samples'), + ('Resolution', 'Resolution', 'Resolution'), + ('Strength', 'Strength', 'Strength'), + ('Color', 'Color', 'Color')], + name='', default='Equidistant Samples', update=update_sockets) + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Equidistant Samples') + + self.add_output('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_set_curve_mesh.py b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_mesh.py new file mode 100644 index 00000000..12822c03 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_mesh.py @@ -0,0 +1,52 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCurveMeshNode(LnxLogicTreeNode): + """Sets curve mesh.""" + bl_idname = 'LNSetCurveMeshNode' + bl_label = 'Set Curve Mesh' + lnx_section = 'set' + lnx_version = 1 + + def update_sockets(self, context): + while len(self.inputs) > 2: + self.inputs.remove(self.inputs[-1]) + if self.property0 == 'Extrude': + self.add_input('LnxFloatSocket', 'Width', default_value = 0.1) + self.add_input('LnxFloatSocket', 'Thickness') + self.add_input('LnxFloatSocket', 'Start') + self.add_input('LnxFloatSocket', 'End', default_value = 1.0) + self.add_input('LnxBoolSocket', 'Fill Caps') + elif self.property0 == 'Bevel': + self.add_input('LnxFloatSocket', 'Depth', default_value = 0.1) + self.add_input('LnxIntSocket', 'Resolution') + self.add_input('LnxFloatSocket', 'Start') + self.add_input('LnxFloatSocket', 'End', default_value = 1.0) + self.add_input('LnxBoolSocket', 'Fill Caps') + else: + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxStringSocket', 'Forward Axis', default_value = 'X') + self.add_input('LnxIntSocket', 'Repetitions', default_value = 1) + self.add_input('LnxFloatSocket', 'Start') + self.add_input('LnxFloatSocket', 'End', default_value = 1.0) + + property0: HaxeEnumProperty( + 'property0', + items = [('Extrude', 'Extrude', 'Extrude'), + ('Bevel', 'Bevel', 'Bevel'), + ('Deform', 'Deform', 'Deform')], + name='', default='Extrude', update=update_sockets) + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxFloatSocket', 'Width', default_value = 0.1) + self.add_input('LnxFloatSocket', 'Thickness') + self.add_input('LnxFloatSocket', 'Start') + self.add_input('LnxFloatSocket', 'End', default_value = 1.0) + self.add_input('LnxBoolSocket', 'Fill Caps') + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketObject', 'Curve Mesh') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_set_curve_shape_key.py b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_shape_key.py new file mode 100644 index 00000000..539a641f --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_shape_key.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCurveShapeKeyNode(LnxLogicTreeNode): + """Sets shape key value of the curve""" + bl_idname = 'LNSetCurveShapeKeyNode' + bl_label = 'Set Curve Shape Key' + lnx_section = 'set' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxStringSocket', 'Shape Key') + self.add_input('LnxFloatSocket', 'Value') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/curve/LN_set_curve_spline.py b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_spline.py new file mode 100644 index 00000000..9cba4f4e --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/LN_set_curve_spline.py @@ -0,0 +1,17 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCurveSplineNode(LnxLogicTreeNode): + """Sets curve spline.""" + bl_idname = 'LNSetCurveSplineNode' + bl_label = 'Set Curve Spline' + lnx_section = 'set' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Curve') + self.add_input('LnxIntSocket', 'Spline Index') + self.add_input('LnxBoolSocket', 'Closed') + self.add_input('LnxIntSocket', 'Resolution', default_value = 12) + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/curve/__init__.py b/leenkx/blender/lnx/logicnode/curve/__init__.py new file mode 100644 index 00000000..206527e9 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/curve/__init__.py @@ -0,0 +1,3 @@ +from lnx.logicnode.lnx_nodes import add_node_section + +add_node_section(name='default', category='curve') diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_camera.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_camera.py index 10375dd1..7b876a4a 100644 --- a/leenkx/blender/lnx/logicnode/draw/LN_draw_camera.py +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_camera.py @@ -16,53 +16,22 @@ class DrawCameraNode(LnxLogicTreeNode): bl_idname = 'LNDrawCameraNode' bl_label = 'Draw Camera' lnx_section = 'draw' - lnx_version = 2 - min_inputs = 7 + lnx_version = 4 num_choices: IntProperty(default=0, min=0) - def __init__(self, *args, **kwargs): - super(DrawCameraNode, self).__init__(*args, **kwargs) - array_nodes[str(id(self))] = self - def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'Start') self.add_input('LnxNodeSocketAction', 'Stop') - self.add_sockets() - - self.add_output('LnxNodeSocketAction', 'On Start') - self.add_output('LnxNodeSocketAction', 'On Stop') - - def add_sockets(self): - self.num_choices += 1 self.add_input('LnxNodeSocketObject', 'Camera ' + str(self.num_choices)) self.add_input('LnxIntSocket', 'X') self.add_input('LnxIntSocket', 'Y') self.add_input('LnxIntSocket', 'Width') self.add_input('LnxIntSocket', 'Height') + self.add_input('LnxBoolSocket', 'Paused') - def remove_sockets(self): - if self.num_choices > 1: - for _ in range(5): - self.inputs.remove(self.inputs.values()[-1]) - self.num_choices -= 1 - - def draw_buttons(self, context, layout): - row = layout.row(align=True) - - op = row.operator('lnx.node_call_func', text='Add Camera', icon='PLUS', emboss=True) - op.node_index = str(id(self)) - op.callback_name = 'add_sockets' - - column = row.column(align=True) - op = column.operator('lnx.node_call_func', text='', icon='X', emboss=True) - op.node_index = str(id(self)) - op.callback_name = 'remove_sockets' - if len(self.inputs) == self.min_inputs: - column.enabled = False + self.add_output('LnxNodeSocketAction', 'On Start') + self.add_output('LnxNodeSocketAction', 'On Stop') def get_replacement_node(self, node_tree: bpy.types.NodeTree): - if self.lnx_version not in (0, 1): - raise LookupError() - return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_gif.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_gif.py new file mode 100644 index 00000000..6448f659 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_gif.py @@ -0,0 +1,49 @@ +from lnx.logicnode.lnx_nodes import * + + +class DrawGifNode(LnxLogicTreeNode): + """Draws an image. + + @input Draw: Activate to draw the image on this frame. The input must + @input In: + be (indirectly) called from an `On Render2D` node. + @input Image: The filename of the image. + @input Color: The color that the image's pixels are multiplied with. + @input Left/Center/Right: Horizontal anchor point of the image. + 0 = Left, 1 = Center, 2 = Right + @input Top/Middle/Bottom: Vertical anchor point of the image. + 0 = Top, 1 = Middle, 2 = Bottom + @input X/Y: Position of the anchor point in pixels. + @input Width/Height: Size of the image in pixels. + @input Angle: Rotation angle in radians. Image will be rotated cloclwiswe + at the anchor point. + + @output Out: Activated after the image has been drawn. + + @see [`kha.graphics2.Graphics.drawImage()`](http://kha.tech/api/kha/graphics2/Graphics.html#drawImage). + """ + bl_idname = 'LNDrawGifNode' + bl_label = 'Draw Gif' + lnx_section = 'draw' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'Draw') + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'Image File') + self.add_input('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('LnxIntSocket', '0/1/2 = Left/Center/Right', default_value=0) + self.add_input('LnxIntSocket', '0/1/2 = Top/Middle/Bottom', default_value=0) + self.add_input('LnxFloatSocket', 'X') + self.add_input('LnxFloatSocket', 'Y') + self.add_input('LnxFloatSocket', 'Width') + self.add_input('LnxFloatSocket', 'Height') + self.add_input('LnxFloatSocket', 'Angle') + self.add_input('LnxIntSocket', 'Start Index') + self.add_input('LnxIntSocket', 'End Index', default_value=-1) + self.add_input('LnxFloatSocket', 'Frame Duration', default_value=1.0) + self.add_input('LnxBoolSocket', 'Loop', default_value=True) + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxIntSocket', 'Total Frames') + self.add_output('LnxIntSocket', 'Index') diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_image_render.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_image_render.py index 643adac8..e6bbf469 100644 --- a/leenkx/blender/lnx/logicnode/draw/LN_draw_image_render.py +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_image_render.py @@ -50,5 +50,6 @@ class DrawImageRenderNode(LnxLogicTreeNode): self.add_input('LnxFloatSocket', 'sHeight') self.add_input('LnxFloatSocket', 'Angle') self.add_input('LnxBoolSocket', 'Render2D') + self.add_input('LnxBoolSocket', 'Continuous') self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_string.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_string.py index 2873760a..cdd95748 100644 --- a/leenkx/blender/lnx/logicnode/draw/LN_draw_string.py +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_string.py @@ -27,6 +27,20 @@ class DrawStringNode(LnxLogicTreeNode): lnx_section = 'draw' lnx_version = 3 + property1: HaxeEnumProperty( + 'property1', + items = [('TextLeft', 'Hor. Align. Left', 'Hor. Align. Left'), + ('TextCenter', 'Hor. Align. Center', 'Hor. Align. Center'), + ('TextRight', 'Hor. Align. Right', 'Hor. Align. Right'),], + name='', default='TextLeft') + + property2: HaxeEnumProperty( + 'property2', + items = [('TextTop', 'Ver. Align. Top', 'Ver. Align. Top'), + ('TextMiddle', 'Ver. Align. Middle', 'Ver. Align. Middle'), + ('TextBottom', 'Ver. Align. Bottom', 'Ver. Align. Bottom'),], + name='', default='TextTop') + def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'Draw') self.add_input('LnxStringSocket', 'String') @@ -41,6 +55,10 @@ class DrawStringNode(LnxLogicTreeNode): self.add_output('LnxFloatSocket', 'Width') self.add_output('LnxFloatSocket', 'Height') + def draw_buttons(self, context, layout): + layout.prop(self, 'property1') + layout.prop(self, 'property2') + def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.lnx_version not in (0, 1, 2): raise LookupError() diff --git a/leenkx/blender/lnx/logicnode/draw/LN_reorder_render2D.py b/leenkx/blender/lnx/logicnode/draw/LN_reorder_render2D.py new file mode 100644 index 00000000..02474cc5 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/draw/LN_reorder_render2D.py @@ -0,0 +1,17 @@ +from lnx.logicnode.lnx_nodes import * + + +class ReorderRender2DNode(LnxLogicTreeNode): + + bl_idname = 'LNReorderRender2DNode' + bl_label = 'Reorder Render2D' + lnx_section = 'draw' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'Render2D') + self.add_input('LnxIntSocket', 'Index') + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxIntSocket', 'Index') diff --git a/leenkx/blender/lnx/logicnode/event/LN_on_render2d.py b/leenkx/blender/lnx/logicnode/event/LN_on_render2d.py index 6e5e008a..26308e05 100644 --- a/leenkx/blender/lnx/logicnode/event/LN_on_render2d.py +++ b/leenkx/blender/lnx/logicnode/event/LN_on_render2d.py @@ -8,7 +8,18 @@ class OnRender2DNode(LnxLogicTreeNode): """ bl_idname = 'LNOnRender2DNode' bl_label = 'On Render2D' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): + self.add_input('LnxIntSocket', 'Index') + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxDynamicSocket', 'Render2D') + self.add_output('LnxIntSocket', 'Index', default_value = -1) + + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.lnx_version not in (0, 1): + raise LookupError() + + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/input/LN_get_gamepad_started.py b/leenkx/blender/lnx/logicnode/input/LN_get_gamepad_started.py index 3670d271..167496ab 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_get_gamepad_started.py +++ b/leenkx/blender/lnx/logicnode/input/LN_get_gamepad_started.py @@ -4,6 +4,7 @@ class GetGamepadStartedNode(LnxLogicTreeNode): """.""" bl_idname = 'LNGetGamepadStartedNode' bl_label = 'Get Gamepad Started' + lnx_section = 'gamepad' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/input/LN_get_image_color.py b/leenkx/blender/lnx/logicnode/input/LN_get_image_color.py new file mode 100644 index 00000000..bb4e71b6 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/input/LN_get_image_color.py @@ -0,0 +1,45 @@ +from lnx.logicnode.lnx_nodes import * + + +class GetImageColorNode(LnxLogicTreeNode): + """Gets Color of the pixel in X,Y position of: input Image, Render, Render2D and Render+Render2D. + + @input X: pixel position regarding width. + @input Y: pixel position regarding height. + + @output Color: Color of the pixel in X,Y position. + + WARNING: Calling getPixels() on a renderTarget with non-standard non-POT dimensions + can cause a system crash. Ensure renderTarget resolution is a power of two + (e.g., 256x256) or a common standard resolution (e.g., 1920x1080). + + """ + bl_idname = 'LNGetImageColorNode' + bl_label = 'Get Image Color' + lnx_section = 'image' + lnx_version = 1 + + def remove_extra_inputs(self, context): + while len(self.inputs) > 0: + self.inputs.remove(self.inputs[-1]) + if self.property0 == 'Image': + self.add_input('LnxStringSocket', 'Image') + self.add_input('LnxIntSocket', 'X') + self.add_input('LnxIntSocket', 'Y') + + property0: HaxeEnumProperty( + 'property0', + items = [('Image', 'Image', 'Image'), + ('Render2D', 'Render2D', 'Render2D'), + ('Render', 'Render', 'Render'), + ('Render&Render2D', 'Render&Render2D', 'Render&Render2D')], + name='', default='Image', update=remove_extra_inputs) + + def lnx_init(self, context): + self.add_input('LnxStringSocket', 'Image') + self.add_input('LnxIntSocket', 'X') + self.add_input('LnxIntSocket', 'Y') + self.add_output('LnxColorSocket', 'Color') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/input/LN_get_keyboard.py b/leenkx/blender/lnx/logicnode/input/LN_get_keyboard.py new file mode 100644 index 00000000..fc2d2893 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/input/LN_get_keyboard.py @@ -0,0 +1,30 @@ +from lnx.logicnode.lnx_nodes import * + +class GetKeyboardNode(LnxLogicTreeNode): + bl_idname = 'LNGetKeyboardNode' + bl_label = 'Get Keyboard' + lnx_section = 'keyboard' + lnx_version = 1 + + def update(self): + self.label = f'{self.bl_label}: {self.property0}' + + def upd(self, context): + self.label = f'{self.bl_label}: {self.property0}' + + property0: HaxeEnumProperty( + 'property0', + items = [('started', 'Started', 'The keyboard button starts to be pressed'), + ('down', 'Down', 'The keyboard button is pressed'), + ('released', 'Released', 'The keyboard button stops being pressed')], + name='', default='started', update=upd) + + def lnx_init(self, context): + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxStringSocket', 'Key') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') + + def draw_label(self) -> str: + return f'{self.bl_label}: {self.property0}' diff --git a/leenkx/blender/lnx/logicnode/input/LN_get_keyboard_started.py b/leenkx/blender/lnx/logicnode/input/LN_get_keyboard_started.py index d97e990c..b3bfeaca 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_get_keyboard_started.py +++ b/leenkx/blender/lnx/logicnode/input/LN_get_keyboard_started.py @@ -4,6 +4,7 @@ class GetKeyboardStartedNode(LnxLogicTreeNode): """.""" bl_idname = 'LNGetKeyboardStartedNode' bl_label = 'Get Keyboard Started' + lnx_section = 'keyboard' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/input/LN_get_mouse_started.py b/leenkx/blender/lnx/logicnode/input/LN_get_mouse_started.py index f9abe016..e2af653d 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_get_mouse_started.py +++ b/leenkx/blender/lnx/logicnode/input/LN_get_mouse_started.py @@ -5,6 +5,7 @@ class GetMouseStartedNode(LnxLogicTreeNode): """.""" bl_idname = 'LNGetMouseStartedNode' bl_label = 'Get Mouse Started' + lnx_section = 'mouse' lnx_version = 2 property0: HaxeBoolProperty( diff --git a/leenkx/blender/lnx/logicnode/input/LN_keyboard.py b/leenkx/blender/lnx/logicnode/input/LN_keyboard.py index 21f74677..03e80ad9 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_keyboard.py +++ b/leenkx/blender/lnx/logicnode/input/LN_keyboard.py @@ -19,7 +19,7 @@ class KeyboardNode(LnxLogicTreeNode): items = [('started', 'Started', 'The keyboard button starts to be pressed'), ('down', 'Down', 'The keyboard button is pressed'), ('released', 'Released', 'The keyboard button stops being pressed')], - name='', default='down', update=upd) + name='', default='started', update=upd) property1: HaxeEnumProperty( 'property1', diff --git a/leenkx/blender/lnx/logicnode/input/LN_mouse.py b/leenkx/blender/lnx/logicnode/input/LN_mouse.py index 5c9176de..1dda9a7b 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_mouse.py +++ b/leenkx/blender/lnx/logicnode/input/LN_mouse.py @@ -26,7 +26,7 @@ class MouseNode(LnxLogicTreeNode): ('down', 'Down', 'The mouse button is pressed'), ('released', 'Released', 'The mouse button stops being pressed'), ('moved', 'Moved', 'Moved')], - name='', default='down', update=upd) + name='', default='started', update=upd) property1: HaxeEnumProperty( 'property1', items = [('left', 'Left', 'Left mouse button'), diff --git a/leenkx/blender/lnx/logicnode/input/LN_set_virtual_button.py b/leenkx/blender/lnx/logicnode/input/LN_set_virtual_button.py new file mode 100644 index 00000000..85e5155d --- /dev/null +++ b/leenkx/blender/lnx/logicnode/input/LN_set_virtual_button.py @@ -0,0 +1,26 @@ +from lnx.logicnode.lnx_nodes import * + +class SetVirtualButtonNode(LnxLogicTreeNode): + bl_idname = 'LNSetVirtualButtonNode' + bl_label = 'Set Virtual Button' + lnx_section = 'virtual' + lnx_version = 1 + + property0: bpy.props.EnumProperty( + items=[('Keyboard', 'Keyboard', 'Keyboard'), + ('Mouse', 'Mouse', 'Mouse'), + ('Gamepad', 'Gamepad', 'Gamepad')], + name='Input', + default='Keyboard' + ) + property1: bpy.props.StringProperty(name='Virtual Button', default='') + property2: bpy.props.StringProperty(name='Physical Button', default='') + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_output('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') + layout.prop(self, 'property1') + layout.prop(self, 'property2') diff --git a/leenkx/blender/lnx/logicnode/input/LN_touch.py b/leenkx/blender/lnx/logicnode/input/LN_touch.py index 17cfb3d6..30505d6b 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_touch.py +++ b/leenkx/blender/lnx/logicnode/input/LN_touch.py @@ -13,7 +13,7 @@ class SurfaceNode(LnxLogicTreeNode): ('down', 'Down', 'The screen surface is touched'), ('released', 'Released', 'The screen surface stops being touched'), ('moved', 'Moved', 'Moved')], - name='', default='down') + name='', default='started') def lnx_init(self, context): self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/light/LN_get_area_light_data.py b/leenkx/blender/lnx/logicnode/light/LN_get_area_light_data.py new file mode 100644 index 00000000..e75f1b67 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/light/LN_get_area_light_data.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class GetAreaLightDataNode(LnxLogicTreeNode): + """Gets the data of the given area light.""" + bl_idname = 'LNGetAreaLightDataNode' + bl_label = 'Get Area Light Data' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Light') + + self.add_output('LnxFloatSocket', 'Size X') + self.add_output('LnxFloatSocket', 'Size Y') diff --git a/leenkx/blender/lnx/logicnode/light/LN_get_light_data.py b/leenkx/blender/lnx/logicnode/light/LN_get_light_data.py new file mode 100644 index 00000000..75bf99ea --- /dev/null +++ b/leenkx/blender/lnx/logicnode/light/LN_get_light_data.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class GetLightDataNode(LnxLogicTreeNode): + """Get lights data info.""" + bl_idname = 'LNGetLightDataNode' + bl_label = 'Get Light Data' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Light') + + self.add_output('LnxIntSocket', 'Type') + self.add_output('LnxFloatSocket', 'Strength') + self.add_output('LnxColorSocket', 'Color') + self.add_output('LnxBoolSocket', 'Shadow') diff --git a/leenkx/blender/lnx/logicnode/light/LN_get_spot_light_data.py b/leenkx/blender/lnx/logicnode/light/LN_get_spot_light_data.py new file mode 100644 index 00000000..46dad10a --- /dev/null +++ b/leenkx/blender/lnx/logicnode/light/LN_get_spot_light_data.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class GetSpotLightDataNode(LnxLogicTreeNode): + """Gets the data of the given spot light.""" + bl_idname = 'LNGetSpotLightDataNode' + bl_label = 'Get Spot Light Data' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Light') + + self.add_output('LnxFloatSocket', 'Size') + self.add_output('LnxFloatSocket', 'Blend') diff --git a/leenkx/blender/lnx/logicnode/lnx_sockets.py b/leenkx/blender/lnx/logicnode/lnx_sockets.py index 839fe4b5..26538b74 100644 --- a/leenkx/blender/lnx/logicnode/lnx_sockets.py +++ b/leenkx/blender/lnx/logicnode/lnx_sockets.py @@ -286,7 +286,7 @@ class LnxRotationSocket(LnxCustomSocket): default_value_unit: EnumProperty( items=[('Deg', 'Degrees', 'Degrees'), ('Rad', 'Radians', 'Radians')], - name='', default='Rad', + name='', default='Deg', update=on_unit_update) default_value_order: EnumProperty( items=[('XYZ','XYZ','XYZ'), diff --git a/leenkx/blender/lnx/logicnode/logic/LN_async_loop.py b/leenkx/blender/lnx/logicnode/logic/LN_async_loop.py new file mode 100644 index 00000000..f1a540d3 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/logic/LN_async_loop.py @@ -0,0 +1,42 @@ +from lnx.logicnode.lnx_nodes import * + +class AsyncLoopNode(LnxLogicTreeNode): + """Resembles a for-loop (`for (i in from...to)`) that is executed at + once when this node is activated. + + @seeNode While + @seeNode Loop Break + + @input From: The value to start the loop from (inclusive) + @input To: The value to end the loop at (exclusive) + + @output Loop: Active at every iteration of the loop + @output Index: The index for the current iteration + @output Done: Activated once when the looping is done + """ + bl_idname = 'LNAsyncLoopNode' + bl_label = 'Async Loop' + bl_description = 'Resembles a for-loop that is executed at once when this node is activated' + lnx_section = 'flow' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxIntSocket', 'From') + self.add_input('LnxIntSocket', 'To') + self.add_input('LnxIntSocket', 'Async Items') + + self.add_output('LnxNodeSocketAction', 'Loop') + self.add_output('LnxIntSocket', 'Index') + self.add_output('LnxNodeSocketAction', 'Done') + + def draw_label(self) -> str: + inp_from = self.inputs['From'] + inp_to = self.inputs['To'] + if inp_from.is_linked and inp_to.is_linked: + return self.bl_label + + val_from = 'x' if inp_from.is_linked else inp_from.default_value_raw + val_to = 'y' if inp_to.is_linked else inp_to.default_value_raw + + return f'{self.bl_label}: {val_from}...{val_to}' diff --git a/leenkx/blender/lnx/logicnode/logic/LN_call_function.py b/leenkx/blender/lnx/logicnode/logic/LN_call_function.py index 4f49ca65..682aefbd 100644 --- a/leenkx/blender/lnx/logicnode/logic/LN_call_function.py +++ b/leenkx/blender/lnx/logicnode/logic/LN_call_function.py @@ -16,7 +16,7 @@ class CallFunctionNode(LnxLogicTreeNode): def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'Trait/Any') + self.add_input('LnxDynamicSocket', 'Trait') self.add_input('LnxStringSocket', 'Function') self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py b/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py index 3a2ea2d0..9ae69ac4 100644 --- a/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py +++ b/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py @@ -22,6 +22,8 @@ class SetCameraRenderFilterNode(LnxLogicTreeNode): self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketObject', 'Object') self.add_input('LnxNodeSocketObject', 'Camera') + self.add_input('LnxBoolSocket', 'Children', default_value=True) + self.add_input('LnxBoolSocket', 'Recursive', default_value=False) self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/material/LN_get_material_image_param.py b/leenkx/blender/lnx/logicnode/material/LN_get_material_image_param.py new file mode 100644 index 00000000..e54c3009 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/material/LN_get_material_image_param.py @@ -0,0 +1,31 @@ +from lnx.logicnode.lnx_nodes import * + +class GetMaterialImageParamNode(LnxLogicTreeNode): + """Set an image value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @output Image: Name of the image. + """ + bl_idname = 'LNGetMaterialImageParamNode' + bl_label = 'Get Material Image Param' + lnx_section = 'params' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Per Object') + self.add_input('LnxDynamicSocket', 'Material') + self.add_input('LnxStringSocket', 'Node') + + self.add_output('LnxStringSocket', 'Image') diff --git a/leenkx/blender/lnx/logicnode/material/LN_get_material_rgb_param.py b/leenkx/blender/lnx/logicnode/material/LN_get_material_rgb_param.py new file mode 100644 index 00000000..5f392756 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/material/LN_get_material_rgb_param.py @@ -0,0 +1,31 @@ +from lnx.logicnode.lnx_nodes import * + +class GetMaterialRgbParamNode(LnxLogicTreeNode): + """Get a color or vector value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @output Color: Color or vector input. + """ + bl_idname = 'LNGetMaterialRgbParamNode' + bl_label = 'Get Material RGB Param' + lnx_section = 'params' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Per Object') + self.add_input('LnxDynamicSocket', 'Material') + self.add_input('LnxStringSocket', 'Node') + + self.add_output('LnxColorSocket', 'Color') diff --git a/leenkx/blender/lnx/logicnode/material/LN_get_material_value_param.py b/leenkx/blender/lnx/logicnode/material/LN_get_material_value_param.py new file mode 100644 index 00000000..1b84fd10 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/material/LN_get_material_value_param.py @@ -0,0 +1,31 @@ +from lnx.logicnode.lnx_nodes import * + +class GetMaterialValueParamNode(LnxLogicTreeNode): + """Get a float value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @output Float: float value. + """ + bl_idname = 'LNGetMaterialValueParamNode' + bl_label = 'Get Material Value Param' + lnx_section = 'params' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Per Object') + self.add_input('LnxDynamicSocket', 'Material') + self.add_input('LnxStringSocket', 'Node') + + self.add_output('LnxFloatSocket', 'Float') diff --git a/leenkx/blender/lnx/logicnode/material/LN_set_object_materials.py b/leenkx/blender/lnx/logicnode/material/LN_set_object_materials.py new file mode 100644 index 00000000..fdb74ef4 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/material/LN_set_object_materials.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class SetMaterialsNode(LnxLogicTreeNode): + """TO DO.""" + bl_idname = 'LNSetMaterialsNode' + bl_label = 'Set Object Materials' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketArray', 'Materials') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/math/LN_colors_mix.py b/leenkx/blender/lnx/logicnode/math/LN_colors_mix.py new file mode 100644 index 00000000..20d33ed5 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/math/LN_colors_mix.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + +class ColorsMixNode(LnxLogicTreeNode): + """ + @see https://github.com/rvanwijnen/spectral.js 2023 Ronald van Wijnen. + """ + + bl_idname = 'LNColorsMixNode' + bl_label = 'Colors Mix' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketArray', 'Colors') + self.add_input('LnxNodeSocketArray', 'Factors') + + self.add_output('LnxColorSocket', 'Color') diff --git a/leenkx/blender/lnx/logicnode/math/LN_math.py b/leenkx/blender/lnx/logicnode/math/LN_math.py index 47e71056..5eb185ae 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_math.py +++ b/leenkx/blender/lnx/logicnode/math/LN_math.py @@ -38,7 +38,10 @@ class MathNode(LnxLogicTreeNode): 'Modulo': 2, 'Less Than': 2, 'Greater Than': 2, - 'Ping-Pong': 2 + 'Ping-Pong': 2, + 'Hyperbolic Sine': 1, + 'Hyperbolic Cosine': 1, + 'Hyperbolic Tangent': 1 }.get(operation_name, 0) def get_enum(self): @@ -97,7 +100,10 @@ class MathNode(LnxLogicTreeNode): ('Fract', 'Fract', 'Fract'), ('Square Root', 'Square Root', 'Square Root'), ('Exponent', 'Exponent', 'Exponent'), - ('Ping-Pong', 'Ping-Pong', 'The output value is moved between 0.0 and the Scale based on the input value')], + ('Ping-Pong', 'Ping-Pong', 'The output value is moved between 0.0 and the Scale based on the input value'), + ('Hyperbolic Sine', 'Hyperbolic Sine', 'Hyperbolic Sine'), + ('Hyperbolic Cosine', 'Hyperbolic Cosine', 'Hyperbolic Cosine'), + ('Hyperbolic Tangent', 'Hyperbolic Tangent', 'Hyperbolic Tangent')], name='', default='Add', set=set_enum, get=get_enum) property1: HaxeBoolProperty('property1', name='Clamp', default=False) diff --git a/leenkx/blender/lnx/logicnode/math/LN_matrix_math.py b/leenkx/blender/lnx/logicnode/math/LN_matrix_math.py deleted file mode 100644 index 72d2654f..00000000 --- a/leenkx/blender/lnx/logicnode/math/LN_matrix_math.py +++ /dev/null @@ -1,22 +0,0 @@ -from lnx.logicnode.lnx_nodes import * - -class MatrixMathNode(LnxLogicTreeNode): - """Multiplies matrices.""" - bl_idname = 'LNMatrixMathNode' - bl_label = 'Matrix Math' - lnx_section = 'matrix' - lnx_version = 1 - - property0: HaxeEnumProperty( - 'property0', - items = [('Multiply', 'Multiply', 'Multiply')], - name='', default='Multiply') - - def lnx_init(self, context): - self.add_input('LnxDynamicSocket', 'Matrix 1') - self.add_input('LnxDynamicSocket', 'Matrix 2') - - self.add_output('LnxDynamicSocket', 'Result') - - def draw_buttons(self, context, layout): - layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/math/LN_poisson_disk.py b/leenkx/blender/lnx/logicnode/math/LN_poisson_disk.py new file mode 100644 index 00000000..60fad711 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/math/LN_poisson_disk.py @@ -0,0 +1,54 @@ +from lnx.logicnode.lnx_nodes import * + +class PoissonDiskNode(LnxLogicTreeNode): + """ + Generates an array of points with a poisson distribution. + + SampleCube (Sample Rectangle): + topLeft: Minimum bounding corner position. + lowerRight: Maximum bounding corner position. + minimumDistance: Minimum required spatial distance between points. + pointsPerIteration: Candidate sample attempts per active point before rejection. + + SampleSphere (Sample Circle): + center: Sphere center position vector. + radius: Bounding sphere radius. + minimumDistance: Minimum required spatial distance between points. + pointsPerIteration: Candidate sample attempts per active point before rejection. + """ + bl_idname = 'LNPoissonDiskNode' + bl_label = 'Poisson Disk Sampling' + lnx_section = 'generator' + lnx_version = 1 + + def remove_extra_inputs(self, context): + while len(self.inputs) > 3: + self.inputs.remove(self.inputs[-1]) + if self.property0 in ('Sample Cube', 'Sample Rectangle'): + self.add_input('LnxVectorSocket', 'Top Left') + self.add_input('LnxVectorSocket', 'Lower Right') + else: + self.add_input('LnxVectorSocket', 'Center') + self.add_input('LnxFloatSocket', 'Radius') + + property0: HaxeEnumProperty( + 'property0', + items = [('Sample Cube', 'Sample Cube', 'Sample Cube'), + ('Sample Sphere', 'Sample Sphere', 'Sample Sphere'), + ('Sample Rectangle', 'Sample Rectangle', 'Sample Rectangle'), + ('Sample Circle', 'Sample Circle', 'Sample Circle')], + name='', default='Sample Cube', update=remove_extra_inputs) + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxIntSocket', 'Points Per Iteration') + self.add_input('LnxFloatSocket', 'Minimum Distance') + self.add_input('LnxVectorSocket', 'Top Left') + self.add_input('LnxVectorSocket', 'Lower Right') + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketArray', 'Points') + self.add_output('LnxIntSocket', 'Length') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/math/LN_procedural_noise.py b/leenkx/blender/lnx/logicnode/math/LN_procedural_noise.py new file mode 100644 index 00000000..c8a7a725 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/math/LN_procedural_noise.py @@ -0,0 +1,71 @@ +from lnx.logicnode.lnx_nodes import * + +class ProceduralNoiseNode(LnxLogicTreeNode): + """ + Generates different kind of noises values. + + repeat: Periodicity interval for seamless tiling. -1 disables repeating. + + Perlin: + x: Spatial X coordinate. + y: Spatial Y coordinate. + z: Spatial Z coordinate. + + OctavePerlin: + x, y, z: Spatial coordinates. + octaves: Number of noise layers combined. + persistence: Amplitude multiplier per octave (decay factor). + frequency: Base spatial scaling factor. + + DiamondSquare: + width: Total grid width. + height: Total grid height. + featureSize: Initial step size for grid subdivision. + scale: Random offset amplitude scale factor. + randFunc: Random number generator function returning a Float. + """ + bl_idname = 'LNProceduralNoiseNode' + bl_label = 'Procedural Noise' + lnx_section = 'generator' + lnx_version = 1 + + def remove_extra_inputs(self, context): + while len(self.inputs) > 1: + self.inputs.remove(self.inputs[-1]) + if self.property0 in ('Perlin', 'Octave Perlin'): + self.add_input('LnxFloatSocket', 'X') + self.add_input('LnxFloatSocket', 'Y') + self.add_input('LnxFloatSocket', 'Z') + self.add_input('LnxIntSocket', 'Repeat', default_value = -1) + if self.property0 == 'Octave Perlin': + self.add_input('LnxIntSocket', 'Octaves', default_value = 4) + self.add_input('LnxFloatSocket', 'Persistence', default_value = 0.5) + self.add_input('LnxFloatSocket', 'Frequency', default_value = 1.0) + if self.property0 == 'Diamond Square': + self.add_input('LnxIntSocket', 'X') + self.add_input('LnxIntSocket', 'Y') + self.add_input('LnxIntSocket', 'Width', default_value = 3) + self.add_input('LnxIntSocket', 'Height', default_value = 3) + self.add_input('LnxIntSocket', 'Feature Size', default_value = 2) + self.add_input('LnxFloatSocket', 'Scale', default_value = 1.0) + self.add_input('LnxFloatSocket', 'Offset', default_value = 0.5) + + property0: HaxeEnumProperty( + 'property0', + items = [('Perlin', 'Perlin', 'Perlin 3D'), + ('Octave Perlin', 'Octave Perlin', 'Octave Perlin 3D'), + ('Diamond Square', 'Diamond Square', 'Diamond Square 2D')], + name='', default='Perlin', update=remove_extra_inputs) + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxFloatSocket', 'X') + self.add_input('LnxFloatSocket', 'Y') + self.add_input('LnxFloatSocket', 'Z') + self.add_input('LnxIntSocket', 'Repeat', default_value = -1) + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxFloatSocket', 'Value') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/math/LN_tween_float.py b/leenkx/blender/lnx/logicnode/math/LN_tween_float.py index 929bf137..ba9a7cac 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_tween_float.py +++ b/leenkx/blender/lnx/logicnode/math/LN_tween_float.py @@ -45,7 +45,13 @@ class TweenFloatNode(LnxLogicTreeNode): ('CircInOut', 'CircInOut', 'CircInOut'), ('BackIn', 'BackIn', 'BackIn'), ('BackOut', 'BackOut', 'BackOut'), - ('BackInOut', 'BackInOut', 'BackInOut')], + ('BackInOut', 'BackInOut', 'BackInOut'), + ('BounceIn', 'BounceIn', 'BounceIn'), + ('BounceOut', 'BounceOut', 'BounceOut'), + ('BounceInOut', 'BounceInOut', 'BounceInOut'), + ('ElasticIn', 'ElasticIn', 'ElasticIn'), + ('ElasticOut', 'ElasticOut', 'ElasticOut'), + ('ElasticInOut', 'ElasticInOut', 'ElasticInOut')], name='', default='Linear') def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/math/LN_tween_rotation.py b/leenkx/blender/lnx/logicnode/math/LN_tween_rotation.py index 9e059e80..b26080d4 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_tween_rotation.py +++ b/leenkx/blender/lnx/logicnode/math/LN_tween_rotation.py @@ -45,7 +45,13 @@ class TweenFloatNode(LnxLogicTreeNode): ('CircInOut', 'CircInOut', 'CircInOut'), ('BackIn', 'BackIn', 'BackIn'), ('BackOut', 'BackOut', 'BackOut'), - ('BackInOut', 'BackInOut', 'BackInOut')], + ('BackInOut', 'BackInOut', 'BackInOut'), + ('BounceIn', 'BounceIn', 'BounceIn'), + ('BounceOut', 'BounceOut', 'BounceOut'), + ('BounceInOut', 'BounceInOut', 'BounceInOut'), + ('ElasticIn', 'ElasticIn', 'ElasticIn'), + ('ElasticOut', 'ElasticOut', 'ElasticOut'), + ('ElasticInOut', 'ElasticInOut', 'ElasticInOut')], name='', default='Linear') def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/math/LN_tween_transform.py b/leenkx/blender/lnx/logicnode/math/LN_tween_transform.py index 319045fe..74c73bf5 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_tween_transform.py +++ b/leenkx/blender/lnx/logicnode/math/LN_tween_transform.py @@ -45,7 +45,13 @@ class TweenTransformNode(LnxLogicTreeNode): ('CircInOut', 'CircInOut', 'CircInOut'), ('BackIn', 'BackIn', 'BackIn'), ('BackOut', 'BackOut', 'BackOut'), - ('BackInOut', 'BackInOut', 'BackInOut')], + ('BackInOut', 'BackInOut', 'BackInOut'), + ('BounceIn', 'BounceIn', 'BounceIn'), + ('BounceOut', 'BounceOut', 'BounceOut'), + ('BounceInOut', 'BounceInOut', 'BounceInOut'), + ('ElasticIn', 'ElasticIn', 'ElasticIn'), + ('ElasticOut', 'ElasticOut', 'ElasticOut'), + ('ElasticInOut', 'ElasticInOut', 'ElasticInOut')], name='', default='Linear') def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/math/LN_tween_vector.py b/leenkx/blender/lnx/logicnode/math/LN_tween_vector.py index caa73d2c..8810f2df 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_tween_vector.py +++ b/leenkx/blender/lnx/logicnode/math/LN_tween_vector.py @@ -45,7 +45,13 @@ class TweenVectorNode(LnxLogicTreeNode): ('CircInOut', 'CircInOut', 'CircInOut'), ('BackIn', 'BackIn', 'BackIn'), ('BackOut', 'BackOut', 'BackOut'), - ('BackInOut', 'BackInOut', 'BackInOut')], + ('BackInOut', 'BackInOut', 'BackInOut'), + ('BounceIn', 'BounceIn', 'BounceIn'), + ('BounceOut', 'BounceOut', 'BounceOut'), + ('BounceInOut', 'BounceInOut', 'BounceInOut'), + ('ElasticIn', 'ElasticIn', 'ElasticIn'), + ('ElasticOut', 'ElasticOut', 'ElasticOut'), + ('ElasticInOut', 'ElasticInOut', 'ElasticInOut')], name='', default='Linear') def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/math/LN_world_to_screen_space.py b/leenkx/blender/lnx/logicnode/math/LN_world_to_screen_space.py index 7d325f86..b85a3252 100644 --- a/leenkx/blender/lnx/logicnode/math/LN_world_to_screen_space.py +++ b/leenkx/blender/lnx/logicnode/math/LN_world_to_screen_space.py @@ -6,7 +6,7 @@ class WorldToScreenSpaceNode(LnxLogicTreeNode): bl_idname = 'LNWorldToScreenSpaceNode' bl_label = 'World to Screen Space' lnx_section = 'matrix' - lnx_version = 2 + lnx_version = 3 def remove_extra_inputs(self, context): while len(self.inputs) > 1: @@ -24,12 +24,14 @@ class WorldToScreenSpaceNode(LnxLogicTreeNode): self.add_input('LnxVectorSocket', 'World') self.add_output('LnxVectorSocket', 'Screen') + self.add_output('LnxIntSocket', 'X') + self.add_output('LnxIntSocket', 'Y') def draw_buttons(self, context, layout): layout.prop(self, 'property0') def get_replacement_node(self, node_tree: bpy.types.NodeTree): - if self.lnx_version not in (0, 1): + if self.lnx_version not in (0, 1, 2): raise LookupError() return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/math/__init__.py b/leenkx/blender/lnx/logicnode/math/__init__.py index 8cdc9214..082f0e8b 100644 --- a/leenkx/blender/lnx/logicnode/math/__init__.py +++ b/leenkx/blender/lnx/logicnode/math/__init__.py @@ -5,3 +5,4 @@ add_node_section(name='angle', category='Math') add_node_section(name='matrix', category='Math') add_node_section(name='color', category='Math') add_node_section(name='vector', category='Math') +add_node_section(name='generator', category='Math') diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_application_time.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_application_time.py index a7e54928..2d6e01f2 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_application_time.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_application_time.py @@ -4,8 +4,17 @@ class TimeNode(LnxLogicTreeNode): """Returns the application execution time and the delta time.""" bl_idname = 'LNTimeNode' bl_label = 'Get Application Time' - lnx_version = 1 + lnx_section = 'time' + lnx_version = 2 def lnx_init(self, context): self.add_output('LnxFloatSocket', 'Time') self.add_output('LnxFloatSocket', 'Delta') + self.add_output('LnxFloatSocket', 'RealTime') + + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.lnx_version not in (0, 1): + raise LookupError() + + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_debug_console_settings.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_debug_console_settings.py index d0eb194e..763275a7 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_debug_console_settings.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_get_debug_console_settings.py @@ -1,7 +1,7 @@ from lnx.logicnode.lnx_nodes import * -class GetDebugConsoleSettings(LnxLogicTreeNode): +class GetDebugConsoleSettingsNode(LnxLogicTreeNode): """Return properties of the debug console. @output Enabled: Whether the debug console is enabled. @@ -17,6 +17,7 @@ class GetDebugConsoleSettings(LnxLogicTreeNode): """ bl_idname = 'LNGetDebugConsoleSettings' bl_label = 'Get Debug Console Settings' + lnx_section = 'debug' lnx_version = 2 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_debug_console_settings.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_debug_console_settings.py index 1b34d90b..8568841c 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -1,9 +1,10 @@ from lnx.logicnode.lnx_nodes import * -class SetDebugConsoleSettings(LnxLogicTreeNode): +class SetDebugConsoleSettingsNode(LnxLogicTreeNode): """Sets the debug console settings.""" bl_idname = 'LNSetDebugConsoleSettings' bl_label = 'Set Debug Console Settings' + lnx_section = 'debug' lnx_version = 1 property0: HaxeEnumProperty( diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_time_scale.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_time_scale.py index 0eab6950..62ede5e5 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_time_scale.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_set_time_scale.py @@ -4,6 +4,7 @@ class SetTimeScaleNode(LnxLogicTreeNode): """Sets the global time scale.""" bl_idname = 'LNSetTimeScaleNode' bl_label = 'Set Time Scale' + lnx_section = 'time' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_sleep.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_sleep.py index 1ce7f611..a03f94ac 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_sleep.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_sleep.py @@ -5,6 +5,7 @@ class SleepNode(LnxLogicTreeNode): through the incoming signal.""" bl_idname = 'LNSleepNode' bl_label = 'Sleep' + lnx_section = 'time' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_timer.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_timer.py index 77d1cdcb..f06758c5 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_timer.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_timer.py @@ -24,6 +24,7 @@ class TimerNode(LnxLogicTreeNode): """ bl_idname = 'LNTimerNode' bl_label = 'Timer' + lnx_section = 'time' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/native/LN_get_assets.py b/leenkx/blender/lnx/logicnode/native/LN_get_assets.py new file mode 100644 index 00000000..13a98359 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/native/LN_get_assets.py @@ -0,0 +1,11 @@ +from lnx.logicnode.lnx_nodes import * + +class GetAssetsNode(LnxLogicTreeNode): + """""" + bl_idname = 'LNGetAssetsNode' + bl_label = 'Get Assets' + lnx_section = 'Assets' + lnx_version = 1 + + def lnx_init(self, context): + self.add_output('LnxNodeSocketArray', 'Assets') diff --git a/leenkx/blender/lnx/logicnode/native/LN_write_gif.py b/leenkx/blender/lnx/logicnode/native/LN_write_gif.py new file mode 100644 index 00000000..1ef668e4 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/native/LN_write_gif.py @@ -0,0 +1,43 @@ +from lnx.logicnode.lnx_nodes import * + + +class WriteGifNode(LnxLogicTreeNode): + """Writes the given image to the given file. If the image + already exists, the existing content of the image is overwritten. + + Aspect ratio must match display resolution ratio. + + Render2D flag to include render draws. + + @input Image File: the name of the image + @input Camera: the render target image of the camera to write to the image file. + @input Width: width of the image file. + @input Height: heigth of the image file. + @input sX: sub position of first x pixel of the sub image (0 for start). + @input sY: sub position of first y pixel of the sub image (0 for start). + @input sWidth: width of the sub image. + @input sHeight: height of the sub image. + @input Render2D: include Render 2D draws. + + @seeNode Read File + """ + bl_idname = 'LNWriteGifNode' + bl_label = 'Write Gif' + lnx_section = 'file' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'Start') + self.add_input('LnxNodeSocketAction', 'Stop') + self.add_input('LnxStringSocket', 'Image File') + self.add_input('LnxNodeSocketObject', 'Camera') + self.add_input('LnxIntSocket', 'Width') + self.add_input('LnxIntSocket', 'Height') + self.add_input('LnxIntSocket', 'sX') + self.add_input('LnxIntSocket', 'sY') + self.add_input('LnxIntSocket', 'sWidth') + self.add_input('LnxIntSocket', 'sHeight') + self.add_input('LnxBoolSocket', 'Render2D') + self.add_input('LnxFloatSocket', 'Frame duration') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_around_navigable_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_around_navigable_location.py new file mode 100644 index 00000000..ea71fda1 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_around_navigable_location.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class AroundNavigableLocationNode(LnxLogicTreeNode): + """A random around a navigable location in the navmesh.""" + bl_idname = 'LNAroundNavigableLocationNode' + bl_label = 'Around NavMesh Location' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxStringSocket', 'NavMeshId', default_value = 'NavMesh') + self.add_input('LnxVectorSocket', 'Position') + self.add_input('LnxFloatSocket', 'Radius') + + self.add_output('LnxVectorSocket', 'Location') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_go_to_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_go_to_location.py index 787a9239..d4402fec 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_go_to_location.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_go_to_location.py @@ -4,17 +4,26 @@ class GoToLocationNode(LnxLogicTreeNode): """Makes a NavCrowd agent go to location. @input In: Start navigation. + @input NavMeshId: The ID of the NavMesh to use. @input Object: The object to navigate. Object must have `NavCrowd` trait applied. @input Location: Closest point on the navmesh to navigate to. + @input Max Speed: Maximum speed of the crowd agent. + @input Max Accelaration: Maximum acceleration of the agent. + @input Turn Speed: Turn rate in range (0, 1). """ bl_idname = 'LNCrowdGoToLocationNode' bl_label = 'Crowd Go to Location' + lnx_section = 'crowd' lnx_version = 1 def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'NavMeshId', default_value = 'NavMesh') self.add_input('LnxNodeSocketObject', 'Object') self.add_input('LnxVectorSocket', 'Location') + self.add_input('LnxFloatSocket', 'Max Speed', 5.0) + self.add_input('LnxFloatSocket', 'Max Accelaration', 100) + self.add_input('LnxFloatSocket', 'Turn Speed', 0.1) self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_set_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_set_location.py new file mode 100644 index 00000000..c34eec13 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_crowd_set_location.py @@ -0,0 +1,21 @@ +from lnx.logicnode.lnx_nodes import * + +class CrowdSetLocationNode(LnxLogicTreeNode): + """Makes a NavCrowd agent go to location. + + @input In: Start teleport. + @input Object: The object to navigate. Object must have `NavCrowd` trait applied. + @input Location: Closest point on the navmesh to navigate to. + """ + bl_idname = 'LNCrowdSetLocationNode' + bl_label = 'Crowd Set Location' + lnx_section = 'crowd' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'NavMeshId', default_value = 'NavMesh') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxVectorSocket', 'Location') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_get_agent_data.py b/leenkx/blender/lnx/logicnode/navmesh/LN_get_agent_data.py index d23d4a19..8ca7370a 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_get_agent_data.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_get_agent_data.py @@ -4,10 +4,13 @@ class GetAgentDataNode(LnxLogicTreeNode): """Gets the speed and turn duration of the agent""" bl_idname = 'LNGetAgentDataNode' bl_label = 'Get Agent Data' - lnx_version = 1 + lnx_section = 'agent' + lnx_version = 2 def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'Object') + self.add_output('LnxStringSocket', 'NavMesh ID') self.add_output('LnxFloatSocket', 'Speed') - self.add_output('LnxFloatSocket', 'Turn Duration') \ No newline at end of file + self.add_output('LnxFloatSocket', 'Turn Duration') + self.add_output('LnxNodeSocketArray', 'Path') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_get_crowd_data.py b/leenkx/blender/lnx/logicnode/navmesh/LN_get_crowd_data.py new file mode 100644 index 00000000..a56c7a0a --- /dev/null +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_get_crowd_data.py @@ -0,0 +1,21 @@ +from lnx.logicnode.lnx_nodes import * + +class GetCrowdDataNode(LnxLogicTreeNode): + """Gets the speed and position of the crowd""" + bl_idname = 'LNGetCrowdDataNode' + bl_label = 'Get Crowd Data' + lnx_section = 'crowd' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + + self.add_output('LnxStringSocket', 'NavMeshId') + self.add_output('LnxVectorSocket', 'Speed') + self.add_output('LnxFloatSocket', 'Max Speed') + self.add_output('LnxFloatSocket', 'Max Acceleration') + self.add_output('LnxFloatSocket', 'Turn Speed') + self.add_output('LnxVectorSocket', 'Location') + self.add_output('LnxVectorSocket', 'Target Location') + self.add_output('LnxIntSocket', 'Id') + self.add_output('LnxNodeSocketArray', 'Path') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_go_to_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_go_to_location.py index 9980cc77..d56e18ed 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_go_to_location.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_go_to_location.py @@ -4,37 +4,28 @@ class GoToLocationNode(LnxLogicTreeNode): """Makes a NavMesh agent go to location. @input In: Start navigation. + @input NavMeshId: The ID of the NavMesh to use. @input Object: The object to navigate. Object must have `NavAgent` trait applied. @input Location: Closest point on the navmesh to navigate to. @input Speed: Rate of movement. @input Turn Duration: Rate of turn. @input Height Offset: Height of the object from the navmesh. - @input Use Raycast: Use physics ray cast to get more precise z positioning. - @input Ray Cast Depth: Depth of ray cast from the object origin. - @input Ray Cast Mask: Ray cast mask for collision detection. - - @output Out: Executed immidiately after start of the navigation. - @output Tick Position: Executed at every step of navigation translation. - @output Tick Rotation: Executed at every step of navigation rotation. """ bl_idname = 'LNGoToLocationNode' - bl_label = 'Go to Location' + bl_label = 'Agent Go to Location' + lnx_section = 'agent' lnx_version = 2 def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'NavMeshId', default_value = 'NavMesh') self.add_input('LnxNodeSocketObject', 'Object') self.add_input('LnxVectorSocket', 'Location') self.add_input('LnxFloatSocket', 'Speed', 5.0) self.add_input('LnxFloatSocket', 'Turn Duration', 0.4) self.add_input('LnxFloatSocket', 'Height Offset', 0.0) - self.add_input('LnxBoolSocket','Use Raycast') - self.add_input('LnxFloatSocket', 'Ray Cast Depth', -5.0) - self.add_input('LnxIntSocket', 'Ray Cast Mask', 1) self.add_output('LnxNodeSocketAction', 'Out') - self.add_output('LnxNodeSocketAction', 'Tick Position') - self.add_output('LnxNodeSocketAction', 'Tick Rotation') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.lnx_version not in (0, 1): diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_navigable_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_navigable_location.py index 66ab3b16..24583575 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_navigable_location.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_navigable_location.py @@ -1,10 +1,12 @@ from lnx.logicnode.lnx_nodes import * class NavigableLocationNode(LnxLogicTreeNode): - """TO DO.""" + """A random navigable location in the navmesh.""" bl_idname = 'LNNavigableLocationNode' - bl_label = 'Navigable Location' - lnx_version = 1 + bl_label = 'Random NavMesh Location' + lnx_version = 2 def lnx_init(self, context): - self.add_output('LnxDynamicSocket', 'Location') + self.add_input('LnxNodeSocketObject', 'NavMesh') + + self.add_output('LnxVectorSocket', 'Location') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_pick_navmesh_location.py b/leenkx/blender/lnx/logicnode/navmesh/LN_pick_navmesh_location.py index 8f6c4f74..b32fa5af 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_pick_navmesh_location.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_pick_navmesh_location.py @@ -1,13 +1,14 @@ from lnx.logicnode.lnx_nodes import * class PickLocationNode(LnxLogicTreeNode): - """Pick a location coordinates in the given NavMesh.""" + """Pick a location on the given NavMesh using screen coordinates.""" bl_idname = 'LNPickLocationNode' bl_label = 'Pick NavMesh Location' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'NavMesh') - self.add_input('LnxVectorSocket', 'Screen Coords') + self.add_input('LnxIntSocket', 'X') + self.add_input('LnxIntSocket', 'Y') self.add_output('LnxVectorSocket', 'Location') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_reconstruct_navmesh.py b/leenkx/blender/lnx/logicnode/navmesh/LN_reconstruct_navmesh.py new file mode 100644 index 00000000..1e06aced --- /dev/null +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_reconstruct_navmesh.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class ReconstructNavMeshNode(LnxLogicTreeNode): + """Reconstruct a given NavMesh.""" + bl_idname = 'LNReconstructNavMeshNode' + bl_label = 'Reconstruct NavMesh' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'NavMeshId', default_value = 'NavMesh') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_stop_agent.py b/leenkx/blender/lnx/logicnode/navmesh/LN_stop_agent.py index d4af7910..e5da9937 100644 --- a/leenkx/blender/lnx/logicnode/navmesh/LN_stop_agent.py +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_stop_agent.py @@ -4,6 +4,7 @@ class StopAgentNode(LnxLogicTreeNode): """Stops the given NavMesh agent.""" bl_idname = 'LNStopAgentNode' bl_label = 'Stop Agent' + lnx_section = 'agent' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/navmesh/LN_stop_crowd.py b/leenkx/blender/lnx/logicnode/navmesh/LN_stop_crowd.py new file mode 100644 index 00000000..aa06369b --- /dev/null +++ b/leenkx/blender/lnx/logicnode/navmesh/LN_stop_crowd.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class StopCrowdNode(LnxLogicTreeNode): + """Stops the given NavMesh agent.""" + bl_idname = 'LNStopCrowdNode' + bl_label = 'Stop Crowd' + lnx_section = 'crowd' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/object/LN_convex_break.py b/leenkx/blender/lnx/logicnode/object/LN_convex_break.py new file mode 100644 index 00000000..1a0334e5 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_convex_break.py @@ -0,0 +1,39 @@ +from lnx.logicnode.lnx_nodes import * + +class ConvexBreakNode(LnxLogicTreeNode): + """Breaks a convex object.""" + + bl_idname = 'LNConvexBreakNode' + bl_label = 'Convex Break' + lnx_version = 1 + + def update_sockets(self, context): + while len(self.inputs) > 2: + self.inputs.remove(self.inputs[-1]) + + if self.property0 == 'Impact': + self.add_input('LnxVectorSocket', 'Impact Point') + self.add_input('LnxVectorSocket', 'Impact Normal') + else: + self.add_input('LnxBoolSocket', 'Random Plane') + self.add_input('LnxVectorSocket', 'Plane') + self.add_input('LnxFloatSocket', 'Scale UV') + self.add_input('LnxBoolSocket', 'Flat Shading', default_value = True) + + property0: HaxeEnumProperty( + 'property0', + items = [('Impact', 'Subdivide by Impact', 'Subdivide by Impact'), + ('Plane', 'Subdivide by Plane', 'Subdivide by Plane')], + name='', default='Impact', update=update_sockets) + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketAction', 'True') + self.add_output('LnxNodeSocketAction', 'False') + self.add_output('LnxNodeSocketArray', 'Array') + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_camera_data.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_camera_data.py new file mode 100644 index 00000000..8feabbd7 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_camera_data.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class GetObjectCameraDataNode(LnxLogicTreeNode): + """Returns the camera data of the given object.""" + bl_idname = 'LNGetObjectCameraDataNode' + bl_label = 'Get Object Camera Data' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + + self.add_output('LnxFloatSocket', 'Camera Distance') + self.add_output('LnxFloatSocket', 'Screen Size') diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_child.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_child.py index 2fc11315..54571ac7 100644 --- a/leenkx/blender/lnx/logicnode/object/LN_get_object_child.py +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_child.py @@ -1,26 +1,42 @@ from lnx.logicnode.lnx_nodes import * class GetChildNode(LnxLogicTreeNode): - """Returns the child of the given object by the child object's name.""" + """Returns the child of the given object by name or by a specific type.""" bl_idname = 'LNGetChildNode' bl_label = 'Get Object Child' lnx_section = 'relations' lnx_version = 1 + def sw_update(self, context): + self.inputs[1].hide = (self.property0 == 'By Type') + property0: HaxeEnumProperty( 'property0', items = [('By Name', 'By Name', 'By Name'), ('Contains', 'Contains', 'Contains'), ('Starts With', 'Starts With', 'Starts With'), ('Ends With', 'Ends With', 'Ends With'), - ], - name='', default='By Name') + ('By Type', 'By Type', 'By Type')], + name='Method', default='By Name', update=sw_update) + + property1: HaxeEnumProperty( + 'property1', + items = [('MeshObject', 'Mesh', 'MeshObject'), + ('CameraObject', 'Camera', 'CameraObject'), + ('LightObject', 'Light', 'LightObject'), + ('SpeakerObject', 'Speaker', 'SpeakerObject'), + ('DecalObject', 'Decal', 'DecalObject'), + ('ProbeObject', 'Probe', 'ProbeObject'), + ('CurveObject', 'Curve', 'CurveObject')], + name='Type', default='MeshObject') def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'Parent') self.add_input('LnxStringSocket', 'Child Name') - self.add_output('LnxNodeSocketObject', 'Child') def draw_buttons(self, context, layout): layout.prop(self, 'property0') + + if self.property0 == 'By Type': + layout.prop(self, 'property1') diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_children.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_children.py index 794971d1..4628396f 100644 --- a/leenkx/blender/lnx/logicnode/object/LN_get_object_children.py +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_children.py @@ -11,3 +11,4 @@ class GetChildrenNode(LnxLogicTreeNode): self.add_input('LnxNodeSocketObject', 'Parent') self.add_output('LnxNodeSocketArray', 'Children') + self.add_output('LnxIntSocket', 'Length') diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_geom.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_geom.py index 492409e1..33a749af 100644 --- a/leenkx/blender/lnx/logicnode/object/LN_get_object_geom.py +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_geom.py @@ -16,10 +16,16 @@ class GetObjectGeomNode(LnxLogicTreeNode): """ bl_idname = 'LNGetObjectGeomNode' - bl_label = 'Get Object Geometry Node' + bl_label = 'Get Object Geometry' lnx_section = 'relations' lnx_version = 1 + property0: HaxeEnumProperty( + 'property0', + items = [('Local', 'Local', 'Local'), + ('Global', 'Global', 'Global')], + name='', default='Local', update='') + def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'Object') @@ -28,5 +34,8 @@ class GetObjectGeomNode(LnxLogicTreeNode): self.add_output('LnxNodeSocketArray', 'Vertices Indices') self.add_output('LnxNodeSocketArray', 'Vertices Material Indices') self.add_output('LnxNodeSocketArray', 'Vertices Face Indices') - self.add_output('LnxDynamicSocket', 'Vertex Groups Maps') - self.add_output('LnxNodeSocketArray', 'Vertex Groups Names') \ No newline at end of file + self.add_output('LnxDynamicSocket', 'Vertex Groups Map') + self.add_output('LnxNodeSocketArray', 'Vertex Groups Names') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_instanced.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_instanced.py new file mode 100644 index 00000000..15f27d36 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_instanced.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + +class GetObjectInstancedNode(LnxLogicTreeNode): + """Gets the object instanced from an array of LOC, ROT and SCL.""" + bl_idname = 'LNGetObjectInstancedNode' + bl_label = 'Get Object Instanced' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + + self.add_output('LnxBoolSocket', 'Instanced', default_value=True) + self.add_output('LnxIntSocket', 'Type') + self.add_output('LnxIntSocket', 'Count') + self.add_output('LnxIntSocket', 'Stride') + self.add_output('LnxNodeSocketArray', 'Array Instanced') diff --git a/leenkx/blender/lnx/logicnode/object/LN_get_object_shape_key.py b/leenkx/blender/lnx/logicnode/object/LN_get_object_shape_key.py new file mode 100644 index 00000000..5eb35ea4 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_get_object_shape_key.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class SetObjectShapeKeyNode(LnxLogicTreeNode): + """Gets shape key value of the object""" + bl_idname = 'LNGetObjectShapeKeyNode' + bl_label = 'Get Object Shape Key' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxStringSocket', 'Shape Key') + + self.add_output('LnxFloatSocket', 'Value') diff --git a/leenkx/blender/lnx/logicnode/object/LN_sample_object_points.py b/leenkx/blender/lnx/logicnode/object/LN_sample_object_points.py new file mode 100644 index 00000000..a2e79537 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_sample_object_points.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + + +class SampleObjectPointsNode(LnxLogicTreeNode): + """ + Samples points and surface normals across a mesh object using a low-discrepancy distribution. + """ + bl_idname = 'LNSampleObjectPointsNode' + bl_label = 'Sample Object Points' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxIntSocket', 'Sample') + + self.add_output('LnxNodeSocketArray', 'Point/Normal') diff --git a/leenkx/blender/lnx/logicnode/object/LN_set_object_instanced.py b/leenkx/blender/lnx/logicnode/object/LN_set_object_instanced.py new file mode 100644 index 00000000..8b4e6a91 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/object/LN_set_object_instanced.py @@ -0,0 +1,27 @@ +from lnx.logicnode.lnx_nodes import * + +class SetObjectInstancedNode(LnxLogicTreeNode): + """Sets the object instanced from an array of LOC, ROT and SCL.""" + bl_idname = 'LNSetObjectInstancedNode' + bl_label = 'Set Object Instanced' + lnx_version = 1 + + property0: HaxeEnumProperty( + 'property0', + items = [('1', 'Loc', 'Instances use their unique position (ipos)'), + ('2', 'Loc + Rot', 'Instances use their unique position and rotation (ipos and irot)'), + ('3', 'Loc + Scale', 'Instances use their unique position and scale (ipos and iscl)'), + ('4', 'Loc + Rot + Scale', 'Instances use their unique position, rotation and scale (ipos, irot, iscl)')], + name='', default='1', update='') + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketArray', 'Array Instanced') + self.add_input('LnxBoolSocket', 'Include original', default_value=False) + self.add_input('LnxBoolSocket', 'Dynamic Usage', default_value=False) + + self.add_output('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/object/LN_set_object_parent.py b/leenkx/blender/lnx/logicnode/object/LN_set_object_parent.py index f1e5008c..d2d6909d 100644 --- a/leenkx/blender/lnx/logicnode/object/LN_set_object_parent.py +++ b/leenkx/blender/lnx/logicnode/object/LN_set_object_parent.py @@ -18,7 +18,7 @@ class SetParentNode(LnxLogicTreeNode): self.add_input('LnxNodeSocketObject', 'Object') self.add_input('LnxNodeSocketObject', 'Parent') self.add_input('LnxBoolSocket', 'Keep Transform', default_value = True) - self.add_input('LnxBoolSocket', 'Parent Inverse') + self.add_input('LnxBoolSocket', 'Parent Inverse', default_value = True) self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/object/LN_set_object_visible.py b/leenkx/blender/lnx/logicnode/object/LN_set_object_visible.py index cb614e65..b23e64b9 100644 --- a/leenkx/blender/lnx/logicnode/object/LN_set_object_visible.py +++ b/leenkx/blender/lnx/logicnode/object/LN_set_object_visible.py @@ -19,7 +19,7 @@ class SetVisibleNode(LnxLogicTreeNode): property0: HaxeEnumProperty( 'property0', - items = [('object', 'Object', 'All object componenets visibility'), + items = [('object', 'Object', 'All object components visibility'), ('mesh', 'Mesh', 'Mesh visibility only'), ('shadow', 'Shadow', 'Shadow visibility only'), ], diff --git a/leenkx/blender/lnx/logicnode/particle/LN_curve_guide.py b/leenkx/blender/lnx/logicnode/particle/LN_curve_guide.py new file mode 100644 index 00000000..6d405bb8 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_curve_guide.py @@ -0,0 +1,17 @@ +from lnx.logicnode.lnx_nodes import * + +class CurveGuideNode(LnxLogicTreeNode): + """Sets the curves guide of the given particle source.""" + bl_idname = 'LNCurveGuideNode' + bl_label = 'Curve Guide' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxIntSocket', 'Slot') + self.add_input('LnxNodeSocketArray', 'Curve Array') + self.add_input('LnxFloatSocket', 'Strengh', default_value = 1.0) + self.add_input('LnxFloatSocket', 'Speed', default_value = 1.0) + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py b/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py index d0de0582..f2873785 100644 --- a/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py +++ b/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py @@ -3,7 +3,7 @@ from lnx.logicnode.lnx_nodes import * class GetParticleNode(LnxLogicTreeNode): """Returns the Particle Systems of an object.""" bl_idname = 'LNGetParticleNode' - bl_label = 'Get Particle' + bl_label = 'Get Particle Systems' lnx_version = 1 def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py b/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py index 3c3ebcf6..889b1638 100644 --- a/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py +++ b/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py @@ -32,3 +32,4 @@ class GetParticleDataNode(LnxLogicTreeNode): self.outputs.new('LnxFloatSocket', 'Lap') self.outputs.new('LnxFloatSocket', 'Lap Time') self.outputs.new('LnxIntSocket', 'Count') + self.outputs.new('LnxNodeSocketObject', 'Particle') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_Add_rigid_body.py b/leenkx/blender/lnx/logicnode/physics/LN_Add_rigid_body.py index 62237107..8b92a83d 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_Add_rigid_body.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_Add_rigid_body.py @@ -15,7 +15,7 @@ class AddRigidBodyNode(LnxLogicTreeNode): @input Animated: Rigid body follows animation and will affect other active non-animated rigid bodies. - @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigd body does not contribute to or receive collissions. + @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigid body does not contribute to or receive collissions. @input Friction: Surface friction of the rigid body. Minimum value = 0, Preferred max value = 1. @@ -77,7 +77,8 @@ class AddRigidBodyNode(LnxLogicTreeNode): ('Cone', 'Cone', 'Cone'), ('Cylinder', 'Cylinder', 'Cylinder'), ('Convex Hull', 'Convex Hull', 'Convex Hull'), - ('Mesh', 'Mesh', 'Mesh')], + ('Mesh', 'Mesh', 'Mesh'), + ('Compound Parent', 'Compound Parent', 'Compound Parent')], name='Shape', default='Box') def lnx_init(self, context): @@ -114,7 +115,7 @@ class AddRigidBodyNode(LnxLogicTreeNode): self.add_input('LnxFloatSocket', 'Linear Damping', 0.04) self.add_input('LnxFloatSocket', 'Angular Damping', 0.1) self.add_input('LnxFloatSocket', 'Angular Friction', 0.1) - self.add_input('LnxBoolSocket', 'Use Deacivation') + self.add_input('LnxBoolSocket', 'Use Deactivation') self.add_input('LnxFloatSocket', 'Linear Velocity Threshold', 0.4) self.add_input('LnxFloatSocket', 'Angular Velocity Threshold', 0.5) self.add_input('LnxIntSocket', 'Collision Group', 1) diff --git a/leenkx/blender/lnx/logicnode/physics/LN_add_physics_hook.py b/leenkx/blender/lnx/logicnode/physics/LN_add_physics_hook.py new file mode 100644 index 00000000..255983b8 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_add_physics_hook.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class AddPhysicsHookNode(LnxLogicTreeNode): + bl_idname = 'LNAddPhysicsHookNode' + bl_label = 'Add Physics Hook' + lnx_section = 'add' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'Hook') + self.add_input('LnxNodeSocketArray', 'Vertices') + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_add_soft_body.py b/leenkx/blender/lnx/logicnode/physics/LN_add_soft_body.py new file mode 100644 index 00000000..3f17f1b6 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_add_soft_body.py @@ -0,0 +1,23 @@ +from lnx.logicnode.lnx_nodes import * + +class AddSoftBodyNode(LnxLogicTreeNode): + bl_idname = 'LNAddSoftBodyNode' + bl_label = 'Add Soft Body' + lnx_section = 'softbody' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + # 0: Cloth, 1: Volume + self.add_input('LnxIntSocket', 'Shape', default_value=0) + self.add_input('LnxFloatSocket', 'Bend', default_value=0.5) + self.add_input('LnxFloatSocket', 'Mass', default_value=1.0) + self.add_input('LnxFloatSocket', 'Margin', default_value=0.04) + self.add_input('LnxFloatSocket', 'Friction', default_value=0.5) + self.add_input('LnxFloatSocket', 'Damping', default_value=0.01) + self.add_input('LnxFloatSocket', 'Linear Stiffness', default_value=0.9) + self.add_input('LnxFloatSocket', 'Angular Stiffness', default_value=0.9) + self.add_input('LnxFloatSocket', 'Pressure', default_value=0.0) + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_get_rb_data.py b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_data.py index 4478d3cd..9262d9cf 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_get_rb_data.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_data.py @@ -8,19 +8,21 @@ class GetRigidBodyDataNode(LnxLogicTreeNode): lnx_version = 1 def lnx_init(self, context): - self.inputs.new('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'RB') - self.outputs.new('LnxBoolSocket', 'Is RB') - self.outputs.new('LnxIntSocket', 'Collision Group') - self.outputs.new('LnxIntSocket', 'Collision Mask') - self.outputs.new('LnxBoolSocket', 'Is Animated') - self.outputs.new('LnxBoolSocket', 'Is Static') - self.outputs.new('LnxFloatSocket', 'Angular Damping') - self.outputs.new('LnxFloatSocket', 'Linear Damping') - self.outputs.new('LnxFloatSocket', 'Friction') - self.outputs.new('LnxFloatSocket', 'Mass') - #self.outputs.new('LnxStringSocket', 'Collision Shape') - #self.outputs.new('LnxIntSocket', 'Activation State') - #self.outputs.new('LnxBoolSocket', 'Is Gravity Enabled') - #self.outputs.new(LnxVectorSocket', Angular Factor') - #self.outputs.new('LnxVectorSocket', Linear Factor') + self.add_output('LnxBoolSocket', 'Is RB') + self.add_output('LnxIntSocket', 'Collision Group') + self.add_output('LnxIntSocket', 'Collision Mask') + self.add_output('LnxBoolSocket', 'Is Animated') + self.add_output('LnxBoolSocket', 'Is Static') + self.add_output('LnxFloatSocket', 'Linear Damping') + self.add_output('LnxFloatSocket', 'Angular Damping') + self.add_output('LnxFloatSocket', 'Friction') + self.add_output('LnxFloatSocket', 'Angular Friction') + self.add_output('LnxFloatSocket', 'Mass') + self.add_output('LnxBoolSocket', 'Is Trigger') + self.add_output('LnxFloatSocket', 'Bounciness') + self.add_output('LnxBoolSocket', 'Gravity Enabled') + self.add_output('LnxVectorSocket', 'Gravity') + self.add_output('LnxVectorSocket', 'Linear Factor') + self.add_output('LnxVectorSocket', 'Angular Factor') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_get_rb_dynamics.py b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_dynamics.py new file mode 100644 index 00000000..05752fff --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_dynamics.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyDynamicsNode(LnxLogicTreeNode): + bl_idname = 'LNSetRigidBodyDynamicsNode' + bl_label = 'Set RB Dynamics' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Is Static', default_value=False) + self.add_input('LnxFloatSocket', 'Mass', default_value=1.0) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_get_rb_point_velocity.py b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_point_velocity.py index a42770db..2212951a 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_get_rb_point_velocity.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_get_rb_point_velocity.py @@ -3,11 +3,11 @@ from lnx.logicnode.lnx_nodes import * class GetPointVelocityNode(LnxLogicTreeNode): """Returns the world velocity of the given point along the rigid body.""" bl_idname = 'LNGetPointVelocityNode' - bl_label = 'Get RB Point Velocity' + bl_label = 'Get RB Velocity at Location' lnx_version = 1 def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'RB') - self.add_input('LnxVectorSocket', 'Point') + self.add_input('LnxVectorSocket', 'Location') self.add_output('LnxVectorSocket', 'Velocity') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_get_sb_data.py b/leenkx/blender/lnx/logicnode/physics/LN_get_sb_data.py new file mode 100644 index 00000000..473ee1e2 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_get_sb_data.py @@ -0,0 +1,22 @@ +from lnx.logicnode.lnx_nodes import * + +class GetSoftBodyDataNode(LnxLogicTreeNode): + bl_idname = 'LNGetSoftBodyDataNode' + bl_label = 'Get SB Data' + lnx_section = 'softbody' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'SB') + + self.add_output('LnxBoolSocket', 'Has SB') + self.add_output('LnxFloatSocket', 'Mass') + self.add_output('LnxFloatSocket', 'Bend') + self.add_output('LnxIntSocket', 'Shape') + self.add_output('LnxFloatSocket', 'Margin') + self.add_output('LnxVectorSocket', 'World Center') + self.add_output('LnxFloatSocket', 'Friction') + self.add_output('LnxFloatSocket', 'Damping') + self.add_output('LnxFloatSocket', 'Pressure') + self.add_output('LnxFloatSocket', 'Linear Stiffness') + self.add_output('LnxFloatSocket', 'Angular Stiffness') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_on_contact_array.py b/leenkx/blender/lnx/logicnode/physics/LN_on_contact_array.py index 69225675..3a5fd0aa 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_on_contact_array.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_on_contact_array.py @@ -19,6 +19,8 @@ class OnContactArrayNode(LnxLogicTreeNode): self.add_input('LnxNodeSocketArray', 'RBs') self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketArray', 'RBs') + self.add_output('LnxNodeSocketObject', 'RB') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_constraint.py b/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_constraint.py new file mode 100644 index 00000000..de4a806e --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_constraint.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class RemovePhysicsConstraintNode(LnxLogicTreeNode): + bl_idname = 'LNRemovePhysicsConstraintNode' + bl_label = 'Remove Physics Constraint' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Pivot Object') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_hook.py b/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_hook.py new file mode 100644 index 00000000..c5df439d --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_remove_physics_hook.py @@ -0,0 +1,12 @@ +from lnx.logicnode.lnx_nodes import * + +class RemovePhysicsHookNode(LnxLogicTreeNode): + bl_idname = 'LNRemovePhysicsHookNode' + bl_label = 'Remove Physics Hook' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_remove_sb.py b/leenkx/blender/lnx/logicnode/physics/LN_remove_sb.py new file mode 100644 index 00000000..c4250be2 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_remove_sb.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class RemoveSoftBodyNode(LnxLogicTreeNode): + bl_idname = 'LNRemoveSoftBodyNode' + bl_label = 'Remove SB' + lnx_section = 'softbody' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'SB') + self.add_input('LnxBoolSocket', 'Restore Geom') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_activation_state.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_activation_state.py index b7c2b85b..c34204df 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_activation_state.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_activation_state.py @@ -4,7 +4,6 @@ class SetActivationStateNode(LnxLogicTreeNode): """Sets the rigid body simulation state of the given object.""" bl_idname = 'LNSetActivationStateNode' bl_label = 'Set RB Activation State' - bl_icon = 'NONE' lnx_version = 1 property0: HaxeEnumProperty( diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_animated.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_animated.py new file mode 100644 index 00000000..28125c1e --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_animated.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyAnimatedNode(LnxLogicTreeNode): + bl_idname = 'LNSetRigidBodyAnimatedNode' + bl_label = 'Set RB Animated' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Animated', default_value=False) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_bounciness.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_bounciness.py new file mode 100644 index 00000000..f2a80881 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_bounciness.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyBouncinessNode(LnxLogicTreeNode): + bl_idname = 'LNSetRigidBodyBouncinessNode' + bl_label = 'Set RB Bounciness' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxFloatSocket', 'Bounciness', default_value=0) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_damping.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_damping.py new file mode 100644 index 00000000..42b7dc88 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_damping.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyDampingNode(LnxLogicTreeNode): + """Sets the linear and angular damping of a rigid body.""" + bl_idname = 'LNSetRigidBodyDampingNode' + bl_label = 'Set RB Damping' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxFloatSocket', 'Linear Damping', default_value=0.04) + self.add_input('LnxFloatSocket', 'Angular Damping', default_value=0.1) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_factor.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_factor.py new file mode 100644 index 00000000..47f0e742 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_factor.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyFactorNode(LnxLogicTreeNode): + bl_idname = 'LNSetRigidBodyFactorNode' + bl_label = 'Set RB Factor' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxVectorSocket', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('LnxVectorSocket', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_friction.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_friction.py index 6def9c0d..a2aa6a8b 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_friction.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_friction.py @@ -1,15 +1,16 @@ from lnx.logicnode.lnx_nodes import * -class SetFrictionNode (LnxLogicTreeNode): +class SetRigidBodyFrictionNode (LnxLogicTreeNode): """Sets the friction of the given rigid body.""" - bl_idname = 'LNSetFrictionNode' + bl_idname = 'LNSetRigidBodyFrictionNode' bl_label = 'Set RB Friction' - bl_icon = 'NONE' + lnx_section = 'props' lnx_version = 1 def lnx_init(self, context): - self.inputs.new('LnxNodeSocketAction', 'In') - self.inputs.new('LnxNodeSocketObject', 'RB') - self.inputs.new('LnxFloatSocket', 'Friction') + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'RB') + self.add_input('LnxFloatSocket', 'Friction', default_value=0.5) + self.add_input('LnxFloatSocket', 'Angular Friction', default_value=0.1) - self.outputs.new('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_gravity.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_gravity.py new file mode 100644 index 00000000..7f775d96 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_gravity.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyGravityNode(LnxLogicTreeNode): + """Sets the gravity for the given rigid body.""" + bl_idname = 'LNSetRigidBodyGravityNode' + bl_label = 'Set RB Gravity' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'RB') + self.add_input('LnxVectorSocket', 'Gravity') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_trigger.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_trigger.py new file mode 100644 index 00000000..39dd2691 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_trigger.py @@ -0,0 +1,13 @@ +from lnx.logicnode.lnx_nodes import * + +class SetRigidBodyTriggerNode(LnxLogicTreeNode): + bl_idname = 'LNSetRigidBodyTriggerNode' + bl_label = 'Set RB Trigger' + lnx_section = 'props' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Is Trigger', default_value=False) + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_velocity.py b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_velocity.py index 955e5d45..b4bd43c5 100644 --- a/leenkx/blender/lnx/logicnode/physics/LN_set_rb_velocity.py +++ b/leenkx/blender/lnx/logicnode/physics/LN_set_rb_velocity.py @@ -2,16 +2,35 @@ from lnx.logicnode.lnx_nodes import * class SetVelocityNode(LnxLogicTreeNode): """Sets the velocity of the given rigid body.""" - bl_idname = 'LNSetVelocityNode' + bl_idname = 'LNSetRigidBodyVelocityNode' bl_label = 'Set RB Velocity' + lnx_section = 'props' lnx_version = 1 + def update_sockets(self, context): + self.inputs[2].hide = self.property0 == 'Angular' + self.inputs[3].hide = self.property0 == 'Angular' + self.inputs[4].hide = self.property0 == 'Linear' + self.inputs[5].hide = self.property0 == 'Linear' + + property0: HaxeEnumProperty( + 'property0', + items=[('Both', 'Both', 'Set both velocities'), + ('Linear', 'Linear', 'Set only linear velocity'), + ('Angular', 'Angular', 'Set only angular velocity')], + name='Velocity', + default='Both', + update=update_sockets + ) + def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketObject', 'RB') self.add_input('LnxVectorSocket', 'Linear') - self.add_input('LnxVectorSocket', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('LnxBoolSocket', 'Linear On Local Axis') self.add_input('LnxVectorSocket', 'Angular') - self.add_input('LnxVectorSocket', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) - + self.add_input('LnxBoolSocket', 'Angular On Local Axis') self.add_output('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/postprocess/LN_get_camera_post_process.py b/leenkx/blender/lnx/logicnode/postprocess/LN_get_camera_post_process.py index 19b6c8c7..495c0028 100644 --- a/leenkx/blender/lnx/logicnode/postprocess/LN_get_camera_post_process.py +++ b/leenkx/blender/lnx/logicnode/postprocess/LN_get_camera_post_process.py @@ -13,6 +13,8 @@ class CameraGetNode(LnxLogicTreeNode): self.add_output('LnxFloatSocket', 'Exposure Compensation')#3 self.add_output('LnxFloatSocket', 'Fisheye Distortion')#4 self.add_output('LnxBoolSocket', 'Auto Focus')#5 + self.add_output('LnxVectorSocket', 'Auto Focus Value') + self.add_output('LnxFloatSocket', 'DOF Max Blur') self.add_output('LnxFloatSocket', 'DOF Distance')#6 self.add_output('LnxFloatSocket', 'DOF Length')#7 self.add_output('LnxFloatSocket', 'DOF F-Stop')#8 diff --git a/leenkx/blender/lnx/logicnode/postprocess/LN_set_camera_post_process.py b/leenkx/blender/lnx/logicnode/postprocess/LN_set_camera_post_process.py index b1711a41..6de62926 100644 --- a/leenkx/blender/lnx/logicnode/postprocess/LN_set_camera_post_process.py +++ b/leenkx/blender/lnx/logicnode/postprocess/LN_set_camera_post_process.py @@ -22,6 +22,10 @@ class CameraSetNode(LnxLogicTreeNode): self.add_input('LnxFloatSocket', 'Fisheye Distortion', default_value=0.01)#4 if self.property0 == 'Auto Focus': self.add_input('LnxBoolSocket', 'Auto Focus', default_value=True)#5 + if self.property0 == 'Auto Focus Value': + self.add_input('LnxVectorSocket', 'Auto Focus Value', default_value=[0.5, 0.5, 0]) + if self.property0 == 'DoF Max Blur': + self.add_input('LnxFloatSocket', 'DoF Max Blur', default_value=1.0) if self.property0 == 'DoF Distance': self.add_input('LnxFloatSocket', 'DoF Distance', default_value=10.0)#6 if self.property0 == 'DoF Length': @@ -50,6 +54,8 @@ class CameraSetNode(LnxLogicTreeNode): ('Exposure Compensation', 'Exposure Compensation', 'Exposure Compensation'), ('Fisheye Distortion', 'Fisheye Distortion', 'Fisheye Distortion'), ('Auto Focus', 'Auto Focus', 'Auto Focus'), + ('Auto Focus Value', 'Auto Focus Value', 'Auto Focus Value'), + ('DoF Max Blur', 'DoF Max Blur', 'DoF Max Blur'), ('DoF Distance', 'DoF Distance', 'DoF Distance'), ('DoF Length', 'DoF Length', 'DoF Length'), ('DoF F-Stop', 'DoF F-Stop', 'DoF F-Stop'), diff --git a/leenkx/blender/lnx/logicnode/random/LN_random_color.py b/leenkx/blender/lnx/logicnode/random/LN_random_color.py index 3ae807db..0f094a01 100644 --- a/leenkx/blender/lnx/logicnode/random/LN_random_color.py +++ b/leenkx/blender/lnx/logicnode/random/LN_random_color.py @@ -5,7 +5,88 @@ class RandomColorNode(LnxLogicTreeNode): """Generates a random color.""" bl_idname = 'LNRandomColorNode' bl_label = 'Random Color' - lnx_version = 1 + lnx_version = 2 + + property0: HaxeBoolProperty( + 'property0', + name='R', + default=True + ) + + property1: HaxeBoolProperty( + 'property1', + name='G', + default=True + ) + + property2: HaxeBoolProperty( + 'property2', + name='B', + default=True + ) + + property3: HaxeBoolProperty( + 'property3', + name='A', + default=False + ) + + property4: HaxeFloatProperty( + 'property4', + name='R Value', + default=0.0, + min=0.0, + max=1.0 + ) + + property5: HaxeFloatProperty( + 'property5', + name='G Value', + default=0.0, + min=0.0, + max=1.0 + ) + + property6: HaxeFloatProperty( + 'property6', + name='B Value', + default=0.0, + min=0.0, + max=1.0 + ) + + property7: HaxeFloatProperty( + 'property7', + name='A Value', + default=1.0, + min=0.0, + max=1.0 + ) + def lnx_init(self, context): self.add_output('LnxColorSocket', 'Color') + + + def draw_buttons(self, context, layout): + + for flag, value, label in [ + ('property0', 'property4', 'R'), + ('property1', 'property5', 'G'), + ('property2', 'property6', 'B'), + ('property3', 'property7', 'A'), + ]: + row = layout.row(align=True) + + row.prop(self, flag, text=label) + + value_row = row.row(align=True) + value_row.enabled = not getattr(self, flag) + value_row.prop(self, value, text='') + + + def get_replacement_node(self, node_tree): + if self.lnx_version not in (0, 1): + raise LookupError() + + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/random/LN_random_object.py b/leenkx/blender/lnx/logicnode/random/LN_random_object.py new file mode 100644 index 00000000..cbbbe7e5 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/random/LN_random_object.py @@ -0,0 +1,25 @@ +from lnx.logicnode.lnx_nodes import * + +class RandomObjectNode(LnxLogicTreeNode): + """Spawns a random object.""" + + bl_idname = 'LNRandomObjectNode' + bl_label = 'Random Object' + lnx_section = 'logic' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'Name') + self.add_input('LnxDynamicSocket', 'Transform') + self.add_input('LnxDynamicSocket', 'Material') + self.add_input('LnxIntSocket', 'NumPoints', default_value = 4) + self.add_input('LnxBoolSocket', 'Mirror X') + self.add_input('LnxBoolSocket', 'Mirror Y') + self.add_input('LnxBoolSocket', 'Mirror Z') + self.add_input('LnxFloatSocket', 'Scale UV', default_value = 0.3) + self.add_input('LnxBoolSocket', 'Flat Shading', default_value = True) + + + self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketObject', 'Object') diff --git a/leenkx/blender/lnx/logicnode/renderpath/LN_set_shader_uniform.py b/leenkx/blender/lnx/logicnode/renderpath/LN_set_shader_uniform.py index c7e1d612..bceaa64a 100644 --- a/leenkx/blender/lnx/logicnode/renderpath/LN_set_shader_uniform.py +++ b/leenkx/blender/lnx/logicnode/renderpath/LN_set_shader_uniform.py @@ -18,8 +18,11 @@ class SetShaderUniformNode(LnxLogicTreeNode): self.add_input('LnxIntSocket', 'Int') elif self.property0 == 'float': self.add_input('LnxFloatSocket', 'Float') - elif self.property0 in ('vec2', 'vec3', 'vec4'): + elif self.property0 in ('vec2', 'vec3'): self.add_input('LnxVectorSocket', 'Vector') + elif self.property0 in ('vec4'): + self.add_input('LnxColorSocket', 'Color') + else: self.add_input('LnxStringSocket', 'Sample2D') property0: HaxeEnumProperty( 'property0', @@ -27,7 +30,8 @@ class SetShaderUniformNode(LnxLogicTreeNode): ('float', 'float', 'float'), ('vec2', 'vec2', 'vec2'), ('vec3', 'vec3', 'vec3'), - ('vec4', 'vec4', 'vec4')], + ('vec4', 'vec4', 'vec4'), + ('sampler2D', 'sampler2D', 'sampler2D')], name='Uniform Type', default='float', description="The type of the uniform", diff --git a/leenkx/blender/lnx/logicnode/scene/LN_collection.py b/leenkx/blender/lnx/logicnode/scene/LN_collection.py index 3134c7c1..8e278ff0 100644 --- a/leenkx/blender/lnx/logicnode/scene/LN_collection.py +++ b/leenkx/blender/lnx/logicnode/scene/LN_collection.py @@ -10,12 +10,16 @@ class GroupNode(LnxLogicTreeNode): bl_idname = 'LNGroupNode' bl_label = 'Collection' lnx_section = 'collection' - lnx_version = 1 + lnx_version = 2 property0: HaxePointerProperty('property0', name='', type=bpy.types.Collection) def lnx_init(self, context): self.add_output('LnxNodeSocketArray', 'Array') + self.add_output('LnxIntSocket', 'Length') def draw_buttons(self, context, layout): layout.prop_search(self, 'property0', bpy.data, 'collections', icon='NONE', text='') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/scene/LN_get_collection.py b/leenkx/blender/lnx/logicnode/scene/LN_get_collection.py index 1be3c13c..3c019949 100644 --- a/leenkx/blender/lnx/logicnode/scene/LN_get_collection.py +++ b/leenkx/blender/lnx/logicnode/scene/LN_get_collection.py @@ -8,9 +8,13 @@ class GetGroupNode(LnxLogicTreeNode): bl_idname = 'LNGetGroupNode' bl_label = 'Get Collection' lnx_section = 'collection' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): self.add_input('LnxStringSocket', 'Name') self.add_output('LnxNodeSocketArray', 'Objects') + self.add_output('LnxIntSocket', 'Length') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/scene/LN_get_object_collection.py b/leenkx/blender/lnx/logicnode/scene/LN_get_object_collection.py index 882843c7..c7ab83c7 100644 --- a/leenkx/blender/lnx/logicnode/scene/LN_get_object_collection.py +++ b/leenkx/blender/lnx/logicnode/scene/LN_get_object_collection.py @@ -1,9 +1,9 @@ from lnx.logicnode.lnx_nodes import * class GetObjectGroupNode(LnxLogicTreeNode): - """Get Object collection.""" + """Get Object collections.""" bl_idname = 'LNGetObjectGroupNode' - bl_label = 'Get Object Collection' + bl_label = 'Get Object Collections' lnx_section = 'collection' lnx_version = 1 diff --git a/leenkx/blender/lnx/logicnode/scene/LN_get_scene_active_object.py b/leenkx/blender/lnx/logicnode/scene/LN_get_scene_active_object.py new file mode 100644 index 00000000..ae2a1353 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/scene/LN_get_scene_active_object.py @@ -0,0 +1,10 @@ +from lnx.logicnode.lnx_nodes import * + +class ActiveSceneObjectNode(LnxLogicTreeNode): + """Returns the active scene.""" + bl_idname = 'LNActiveSceneObjectNode' + bl_label = 'Get Scene Active Object' + lnx_version = 1 + + def lnx_init(self, context): + self.add_output('LnxNodeSocketObject', 'Scene') diff --git a/leenkx/blender/lnx/logicnode/sound/LN_get_position_speaker.py b/leenkx/blender/lnx/logicnode/sound/LN_get_position_speaker.py deleted file mode 100644 index 571bc99d..00000000 --- a/leenkx/blender/lnx/logicnode/sound/LN_get_position_speaker.py +++ /dev/null @@ -1,11 +0,0 @@ -from lnx.logicnode.lnx_nodes import * - -class GetPositionSpeakerNode(LnxLogicTreeNode): - """Gets the current playback position of the given speaker object in seconds.""" - bl_idname = 'LNGetPositionSpeakerNode' - bl_label = 'Get Position Speaker' - lnx_version = 1 - - def lnx_init(self, context): - self.add_input('LnxNodeSocketObject', 'Speaker') - self.add_output('LnxFloatSocket', 'Position (seconds)') diff --git a/leenkx/blender/lnx/logicnode/sound/LN_get_speaker_data.py b/leenkx/blender/lnx/logicnode/sound/LN_get_speaker_data.py new file mode 100644 index 00000000..a1954492 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/sound/LN_get_speaker_data.py @@ -0,0 +1,16 @@ +from lnx.logicnode.lnx_nodes import * + +class GetSpeakerDataNode(LnxLogicTreeNode): + """Gets the data of the given speaker.""" + bl_idname = 'LNGetSpeakerDataNode' + bl_label = 'Get Speaker Data' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Speaker') + + self.add_output('LnxFloatSocket', 'Volume') + self.add_output('LnxFloatSocket', 'Pitch') + self.add_output('LnxIntSocket', 'Channels') + self.add_output('LnxNodeSocketArray', 'Length') + self.add_output('LnxNodeSocketArray', 'Position') diff --git a/leenkx/blender/lnx/logicnode/sound/LN_play_sound.py b/leenkx/blender/lnx/logicnode/sound/LN_play_sound.py index 2466230f..3af9e6eb 100644 --- a/leenkx/blender/lnx/logicnode/sound/LN_play_sound.py +++ b/leenkx/blender/lnx/logicnode/sound/LN_play_sound.py @@ -12,7 +12,7 @@ class PlaySoundNode(LnxLogicTreeNode): nothing happens. @input Stop: Stops the playing sound. If the playback is paused, this will reset the playback position to the start of the sound. - @input Update Volume: Updates the volume of the current playback. + @input Set Volume: Updates the volume of the current playback. @input Volume: Volume of the playback. Typically ranges from 0 to 1. @output Out: activated once when Play is activated. @@ -27,18 +27,18 @@ class PlaySoundNode(LnxLogicTreeNode): @option Retrigger: If true, the playback position will be reset to the beginning on each activation of Play. If false, the playback will continue at the current position. - @option Sample Rate: Manually override the sample rate of the sound - (this controls the pitch and the playback speed). + @option Pitch: this controls the pitch and the playback speed. """ bl_idname = 'LNPlaySoundRawNode' bl_label = 'Play Sound' bl_width_default = 200 - lnx_version = 3 + lnx_section = 'raw' + lnx_version = 5 def remove_extra_inputs(self, context): - while len(self.inputs) > 5: + while len(self.inputs) > 6: self.inputs.remove(self.inputs[-1]) - if self.property6 == 'Sound Name': + if self.property4 == 'Sound Name': self.add_input('LnxStringSocket', 'Sound Name') property0: HaxePointerProperty('property0', name='', type=bpy.types.Sound) @@ -55,24 +55,12 @@ class PlaySoundNode(LnxLogicTreeNode): default=False) property3: HaxeBoolProperty( 'property3', - name='Use Custom Sample Rate', - description='If enabled, override the default sample rate', - default=False) - property4: HaxeIntProperty( - 'property4', - name='Sample Rate', - description='Set the sample rate used to play this sound', - default=44100, - min=0) - property5: HaxeBoolProperty( - 'property5', name='Stream', description='Stream the sound from disk', - default=False - ) + default=False) - property6: HaxeEnumProperty( - 'property6', + property4: HaxeEnumProperty( + 'property4', items = [('Sound', 'Sound', 'Sound'), ('Sound Name', 'Sound Name', 'Sound Name')], name='', default='Sound', update=remove_extra_inputs) @@ -81,36 +69,27 @@ class PlaySoundNode(LnxLogicTreeNode): self.add_input('LnxNodeSocketAction', 'Play') self.add_input('LnxNodeSocketAction', 'Pause') self.add_input('LnxNodeSocketAction', 'Stop') - self.add_input('LnxNodeSocketAction', 'Update Volume') + self.add_input('LnxNodeSocketAction', 'Set Volume') self.add_input('LnxFloatSocket', 'Volume', default_value=1.0) + self.add_input('LnxFloatSocket', 'Pitch', default_value=1.0) self.add_output('LnxNodeSocketAction', 'Out') self.add_output('LnxNodeSocketAction', 'Is Running') self.add_output('LnxNodeSocketAction', 'Done') + self.add_output('LnxFloatSocket', 'Length') + self.add_output('LnxFloatSocket', 'Position') def draw_buttons(self, context, layout): - layout.prop(self, 'property6') + layout.prop(self, 'property4') col = layout.column(align=True) - if self.property6 == 'Sound': + if self.property4 == 'Sound': col.prop_search(self, 'property0', bpy.data, 'sounds', icon='NONE', text='') - col.prop(self, 'property5') + col.prop(self, 'property3') col.prop(self, 'property1') col.prop(self, 'property2') - layout.label(text="Overrides:") - # Sample rate - split = layout.split(factor=0.15, align=False) - split.prop(self, 'property3', text="") - row = split.row() - if not self.property3: - row.enabled = False - row.prop(self, 'property4') - def get_replacement_node(self, node_tree: bpy.types.NodeTree): - if 0 <= self.lnx_version <= 2: - return NodeReplacement.Identity(self) - - raise LookupError() + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/sound/LN_play_speaker.py b/leenkx/blender/lnx/logicnode/sound/LN_play_speaker.py index 416722b4..2d27bd22 100644 --- a/leenkx/blender/lnx/logicnode/sound/LN_play_speaker.py +++ b/leenkx/blender/lnx/logicnode/sound/LN_play_speaker.py @@ -14,5 +14,9 @@ class PlaySpeakerNode(LnxLogicTreeNode): def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketObject', 'Speaker') + self.add_input('LnxBoolSocket', 'Retrigger') + self.add_input('LnxBoolSocket', 'Loop') + self.add_input('LnxBoolSocket', 'Stream') self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxNodeSocketAction', 'Done') diff --git a/leenkx/blender/lnx/logicnode/sound/LN_set_pitch_speaker.py b/leenkx/blender/lnx/logicnode/sound/LN_set_pitch_speaker.py new file mode 100644 index 00000000..f7be8e0a --- /dev/null +++ b/leenkx/blender/lnx/logicnode/sound/LN_set_pitch_speaker.py @@ -0,0 +1,18 @@ +from lnx.logicnode.lnx_nodes import * + +class SetPitchSoundNode(LnxLogicTreeNode): + """sets pitch of the given speaker object. + + @seeNode Play Speaker + @seeNode Stop Speaker + """ + bl_idname = 'LNSetPitchSoundNode' + bl_label = 'Set Pitch Speaker' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Speaker') + self.add_input('LnxFloatSocket', 'Pitch') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/sound/LN_set_position_speaker.py b/leenkx/blender/lnx/logicnode/sound/LN_set_position_speaker.py index cd38b3fe..c67629ac 100644 --- a/leenkx/blender/lnx/logicnode/sound/LN_set_position_speaker.py +++ b/leenkx/blender/lnx/logicnode/sound/LN_set_position_speaker.py @@ -12,6 +12,6 @@ class SetPositionSpeakerNode(LnxLogicTreeNode): def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketObject', 'Speaker') - self.add_input('LnxFloatSocket', 'Position (seconds)') + self.add_input('LnxFloatSocket', 'Position') self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/string/LN_format_number.py b/leenkx/blender/lnx/logicnode/string/LN_format_number.py new file mode 100644 index 00000000..58a803ad --- /dev/null +++ b/leenkx/blender/lnx/logicnode/string/LN_format_number.py @@ -0,0 +1,14 @@ +from lnx.logicnode.lnx_nodes import * + +class FormatNumberNode(LnxLogicTreeNode): + bl_idname = 'LNFormatNumberNode' + bl_label = 'Format Number' + lnx_section = 'util' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxFloatSocket', 'Number', default_value=0.0) + self.add_input('LnxStringSocket', 'Thousands Sep', default_value=".") + self.add_input('LnxStringSocket', 'Decimal Sep', default_value=",") + self.add_input('LnxBoolSocket', 'Include Symbol $', default_value=False) + self.add_output('LnxStringSocket', 'Result') diff --git a/leenkx/blender/lnx/logicnode/string/LN_format_time.py b/leenkx/blender/lnx/logicnode/string/LN_format_time.py new file mode 100644 index 00000000..931b4cc0 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/string/LN_format_time.py @@ -0,0 +1,12 @@ +from lnx.logicnode.lnx_nodes import * + +class FormatTimeNode(LnxLogicTreeNode): + bl_idname = 'LNFormatTimeNode' + bl_label = 'Format Time' + lnx_section = 'util' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxFloatSocket', 'Time', default_value=0.0) + self.add_input('LnxStringSocket', 'Format', default_value="HH:MM:SS:MS") + self.add_output('LnxStringSocket', 'Result') diff --git a/leenkx/blender/lnx/logicnode/string/LN_split_string.py b/leenkx/blender/lnx/logicnode/string/LN_split_string.py index 18bdf7a0..511803b0 100644 --- a/leenkx/blender/lnx/logicnode/string/LN_split_string.py +++ b/leenkx/blender/lnx/logicnode/string/LN_split_string.py @@ -4,10 +4,14 @@ class SplitStringNode(LnxLogicTreeNode): """Splits the given string.""" bl_idname = 'LNSplitStringNode' bl_label = 'Split String' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): - self.add_output('LnxNodeSocketArray', 'Array') - self.add_input('LnxStringSocket', 'String') self.add_input('LnxStringSocket', 'Split') + + self.add_output('LnxNodeSocketArray', 'Array') + self.add_output('LnxIntSocket', 'Length') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/trait/LN_add_trait_to_object.py b/leenkx/blender/lnx/logicnode/trait/LN_add_trait_to_object.py index 88ebfd0d..4f895ea6 100644 --- a/leenkx/blender/lnx/logicnode/trait/LN_add_trait_to_object.py +++ b/leenkx/blender/lnx/logicnode/trait/LN_add_trait_to_object.py @@ -4,11 +4,35 @@ class AddTraitNode(LnxLogicTreeNode): """Adds trait to the given object.""" bl_idname = 'LNAddTraitNode' bl_label = 'Add Trait to Object' - lnx_version = 2 + lnx_version = 3 + + def remove_extra_inputs(self, context): + if len(self.inputs) > 1: + self.inputs.remove(self.inputs[-1]) + if self.property0 == 'Trait': + self.add_input('LnxDynamicSocket', 'Trait') + else: + self.add_input('LnxStringSocket', 'TraitName') + + property0: HaxeEnumProperty( + 'property0', + items = [('Trait', 'Trait', 'Trait'), + ('TraitName', 'TraitName', 'TraitName')], + name='', default='TraitName', update=remove_extra_inputs) def lnx_init(self, context): self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketObject', 'Object') - self.add_input('LnxStringSocket', 'Trait') + self.add_input('LnxStringSocket', 'TraitName') self.add_output('LnxNodeSocketAction', 'Out') + self.add_output('LnxDynamicSocket', 'Trait') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.lnx_version not in (0, 2): + raise LookupError() + + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/trait/LN_get_object_traits.py b/leenkx/blender/lnx/logicnode/trait/LN_get_object_traits.py index 2746e31d..c5941fd5 100644 --- a/leenkx/blender/lnx/logicnode/trait/LN_get_object_traits.py +++ b/leenkx/blender/lnx/logicnode/trait/LN_get_object_traits.py @@ -4,9 +4,13 @@ class GetObjectTraitsNode(LnxLogicTreeNode): """Returns all traits from the given object.""" bl_idname = 'LNGetObjectTraitsNode' bl_label = 'Get Object Traits' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'Object') self.add_output('LnxNodeSocketArray', 'Traits') + self.add_output('LnxIntSocket', 'Length') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/transform/LN_get_object_transform.py b/leenkx/blender/lnx/logicnode/transform/LN_get_object_transform.py index dacb576e..3870dddb 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_get_object_transform.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_get_object_transform.py @@ -6,9 +6,16 @@ class GetTransformNode(LnxLogicTreeNode): rotation and scale.""" bl_idname = 'LNGetTransformNode' bl_label = 'Get Object Transform' - lnx_version = 1 + lnx_version = 2 def lnx_init(self, context): self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxBoolSocket', 'Parent Relative') self.add_output('LnxDynamicSocket', 'Transform') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.lnx_version not in (0, 1): + raise LookupError() + + return NodeReplacement.Identity(self) diff --git a/leenkx/blender/lnx/logicnode/transform/LN_rotate_object.py b/leenkx/blender/lnx/logicnode/transform/LN_rotate_object.py index 5793a113..944148d8 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_rotate_object.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_rotate_object.py @@ -51,12 +51,6 @@ class RotateObjectNode(LnxLogicTreeNode): name='', default='Local') - - - - - - def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.lnx_version not in (0, 1): raise LookupError() diff --git a/leenkx/blender/lnx/logicnode/transform/LN_separate_rotation.py b/leenkx/blender/lnx/logicnode/transform/LN_separate_rotation.py index 27a237eb..b33993c4 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_separate_rotation.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_separate_rotation.py @@ -12,19 +12,22 @@ class SeparateRotationNode(LnxLogicTreeNode): self.add_output('LnxVectorSocket', 'Euler Angles / Vector XYZ') self.add_output('LnxFloatSocket', 'Angle / W') - + + self.outputs[1].hide = True def on_property_update(self, context): """called by the EnumProperty, used to update the node socket labels""" if self.property0 == 'Quaternion': self.outputs[0].name = "Quaternion XYZ" self.outputs[1].name = "Quaternion W" + self.outputs[1].hide = False elif self.property0 == 'EulerAngles': self.outputs[0].name = "Euler Angles" - self.outputs[1].name = "[unused for Euler output]" + self.outputs[1].hide = True elif self.property0 == 'AxisAngle': self.outputs[0].name = "Axis" self.outputs[1].name = "Angle" + self.outputs[1].hide = False else: raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_separate_rotation.py') diff --git a/leenkx/blender/lnx/logicnode/transform/LN_set_object_dimension.py b/leenkx/blender/lnx/logicnode/transform/LN_set_object_dimension.py new file mode 100644 index 00000000..f2d44450 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/transform/LN_set_object_dimension.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class SetDimensionNode(LnxLogicTreeNode): + """Sets the dimension of the given object.""" + bl_idname = 'LNSetDimensionNode' + bl_label = 'Set Object Dimension' + lnx_section = 'dimension' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxVectorSocket', 'Dimension', default_value=[1.0, 1.0, 1.0]) + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/logicnode/transform/LN_set_object_rotation.py b/leenkx/blender/lnx/logicnode/transform/LN_set_object_rotation.py index b4bc20ad..95801b56 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_set_object_rotation.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_set_object_rotation.py @@ -6,7 +6,7 @@ class SetRotationNode(LnxLogicTreeNode): bl_idname = 'LNSetRotationNode' bl_label = 'Set Object Rotation' lnx_section = 'rotation' - lnx_version = 2 + lnx_version = 3 def lnx_init(self, context): @@ -16,15 +16,42 @@ class SetRotationNode(LnxLogicTreeNode): self.add_output('LnxNodeSocketAction', 'Out') + def draw_buttons(self, context, layout): + layout.prop(self, 'property0_proxy') + + def on_proxyproperty_update(self, context=None): + self.property0 = self.property0_proxy + print(self.property0) + + property0_proxy: EnumProperty( + items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'), + ('Global', 'Global/Parent F.O.R.', + 'Frame of reference oriented with the object\'s parent or the world')], + name='', default='Local', + update = on_proxyproperty_update + ) + property0: HaxeEnumProperty( + 'property0', + items=[('Euler Angles', 'NODE REPLACEMENT ONLY', ''), + ('Angle Axies (Radians)', 'NODE REPLACEMENT ONLY', ''), + ('Angle Axies (Degrees)', 'NODE REPLACEMENT ONLY', ''), + ('Quaternion', 'NODE REPLACEMENT ONLY', ''), + ('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'), + ('Global', 'Global/Parent F.O.R.', + 'Frame of reference oriented with the object\'s parent or the world') + ], + name='', default='Local') + def get_replacement_node(self, node_tree: bpy.types.NodeTree): - if self.lnx_version not in (0, 1): + if self.lnx_version not in (0, 1, 2): raise LookupError() + if self.lnx_version == 2: + return NodeReplacement.Identity(self) # transition from version 1 to version 2: make rotations their own sockets # this transition is a mess, I know. - newself = self.id_data.nodes.new('LNRotateObjectNode') inputnode = self.id_data.nodes.new('LNRotationNode') @@ -66,11 +93,3 @@ class SetRotationNode(LnxLogicTreeNode): ) newself.inputs[2].default_value_raw = quat return [newself, inputnode] - - # note: this is unused, but kept here so that the 'property0' field can be read during node replacement - property0: EnumProperty( - items = [('Euler Angles', '',''), - ('Angle Axies (Radians)', '', ''), - ('Angle Axies (Degrees)', '', ''), - ('Quaternion', '', '')], - name='', default='Euler Angles') diff --git a/leenkx/blender/lnx/logicnode/transform/LN_transform_math.py b/leenkx/blender/lnx/logicnode/transform/LN_transform_math.py index 9fc6ad4a..373f85e0 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_transform_math.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_transform_math.py @@ -1,13 +1,31 @@ from lnx.logicnode.lnx_nodes import * +def update_node(self, context): + if self.property0 == 'Invert': + self.inputs[1].hide = True + else: + self.inputs[1].hide = False + class TransformMathNode(LnxLogicTreeNode): - """Operates the two given transform values.""" + """Performs mathematical operations on transformation matrices.""" bl_idname = 'LNTransformMathNode' bl_label = 'Transform Math' - lnx_version = 1 + lnx_version = 2 + + property0: HaxeEnumProperty( + 'property0', + items = [ + ('Multiply', 'Multiply', 'Standard matrix multiplication combining position, rotation, and scale'), + ('Invert', 'Invert', 'Calculates the inverse matrix, reversing all transformations'), + ('Transform Math', 'Transform Math', 'Multiplies rotation and scale while directly adding translations') + ], + name='', default='Multiply', update=update_node) def lnx_init(self, context): self.add_input('LnxDynamicSocket', 'Transform 1') self.add_input('LnxDynamicSocket', 'Transform 2') self.add_output('LnxDynamicSocket', 'Result') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/leenkx/blender/lnx/logicnode/variable/LN_math_constants.py b/leenkx/blender/lnx/logicnode/variable/LN_math_constants.py new file mode 100644 index 00000000..15730d92 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/variable/LN_math_constants.py @@ -0,0 +1,12 @@ +from lnx.logicnode.lnx_nodes import * + +class MathConstantsNode(LnxLogicTreeNode): + bl_idname = 'LNMathConstantsNode' + bl_label = 'Math Constants' + lnx_section = 'math' + lnx_version = 1 + + def lnx_init(self, context): + self.add_output('LnxFloatSocket', 'Negative Infinity') + self.add_output('LnxFloatSocket', 'Positive Infinity') + self.add_output('LnxFloatSocket', 'PI') diff --git a/leenkx/blender/lnx/logicnode/variable/LN_rotation.py b/leenkx/blender/lnx/logicnode/variable/LN_rotation.py index 9855b2e5..b03dc4db 100644 --- a/leenkx/blender/lnx/logicnode/variable/LN_rotation.py +++ b/leenkx/blender/lnx/logicnode/variable/LN_rotation.py @@ -14,18 +14,25 @@ class RotationNode(LnxLogicVariableNodeMixin, LnxLogicTreeNode): self.add_input('LnxFloatSocket', 'Angle / W') self.add_output('LnxRotationSocket', 'Out', is_var=True) + + self.inputs[1].hide = True def on_property_update(self, context): """called by the EnumProperty, used to update the node socket labels""" if self.property0 == "Quaternion": self.inputs[0].name = "Quaternion XYZ" self.inputs[1].name = "Quaternion W" + self.inputs[1].hide = False elif self.property0 == "EulerAngles": self.inputs[0].name = "Euler Angles" - self.inputs[1].name = "[unused for Euler input]" + if self.inputs[1].is_linked: + for link in self.inputs[1].links: + self.id_data.links.remove(link) + self.inputs[1].hide = True elif self.property0 == "AxisAngle": self.inputs[0].name = "Axis" self.inputs[1].name = "Angle" + self.inputs[1].hide = False else: raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_rotation.py') @@ -49,7 +56,7 @@ class RotationNode(LnxLogicVariableNodeMixin, LnxLogicTreeNode): 'property1', items=[('Deg', 'Degrees', 'Degrees'), ('Rad', 'Radians', 'Radians')], - name='', default='Rad') + name='', default='Deg') property2: HaxeEnumProperty( 'property2', items=[('XYZ','XYZ','XYZ'), diff --git a/leenkx/blender/lnx/logicnode/world/LN_get_world_data.py b/leenkx/blender/lnx/logicnode/world/LN_get_world_data.py new file mode 100644 index 00000000..9a7f64ab --- /dev/null +++ b/leenkx/blender/lnx/logicnode/world/LN_get_world_data.py @@ -0,0 +1,12 @@ +from lnx.logicnode.lnx_nodes import * + +class GetWorldDataNode(LnxLogicTreeNode): + """Gets the World data of the active scene.""" + bl_idname = 'LNGetWorldDataNode' + bl_label = 'Get World Data' + lnx_version = 1 + + def lnx_init(self, context): + self.add_output('LnxStringSocket', 'World') + self.add_output('LnxFloatSocket', 'Strength') + self.add_output('LnxColorSocket', 'Color') diff --git a/leenkx/blender/lnx/logicnode/world/LN_set_world_probe.py b/leenkx/blender/lnx/logicnode/world/LN_set_world_probe.py new file mode 100644 index 00000000..9a9a7656 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/world/LN_set_world_probe.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class SetWorldProbeNode(LnxLogicTreeNode): + """Sets the World Probe of the active scene.""" + + bl_idname = 'LNSetWorldProbeNode' + bl_label = 'Set World Probe' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketAction', 'Reset') + self.add_input('LnxStringSocket', 'World') + + self.add_output('LnxNodeSocketAction', 'Out') diff --git a/leenkx/blender/lnx/make.py b/leenkx/blender/lnx/make.py index 8a2705a9..f6730036 100644 --- a/leenkx/blender/lnx/make.py +++ b/leenkx/blender/lnx/make.py @@ -99,7 +99,16 @@ def run_proc(cmd, done: Callable) -> subprocess.Popen: def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants): - os.chdir(raw_shaders_path + '/' + shader_name) + if shader_name == 'compositor_pass': + wrd = bpy.data.worlds['Lnx'] + rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index] + if rpdat.lnx_custom_compositor: + raw_shaders_path = os.path.join(lnx.utils.get_fp(), 'Compositor').replace('\\', '/') + os.chdir(raw_shaders_path) + else: + os.chdir(raw_shaders_path + '/' + shader_name) + else: + os.chdir(raw_shaders_path + '/' + shader_name) # Open json file json_name = shader_name + '.json' @@ -255,7 +264,7 @@ def export_data_impl(fp, sdk_path): navigation_found = False ui_found = False network_found = False - LeenkxExporter.compress_enabled = state.is_publish and wrd.lnx_asset_compression + LeenkxExporter.compress_enabled = wrd.lnx_asset_compression LeenkxExporter.optimize_enabled = state.is_publish and wrd.lnx_optimize_data if not os.path.exists(build_dir + '/compiled/Assets'): os.makedirs(build_dir + '/compiled/Assets') @@ -287,10 +296,28 @@ def export_data_impl(fp, sdk_path): build_cache = BuildExportCache() + initial_window_scene = bpy.context.window.scene + for scene_name, asset_path in scene_targets: # Reset shader comparison arrays to prevent cross-scene shader merging assets.reset_shader_cons() - LeenkxExporter.export_scene(bpy.context, asset_path, scene=bpy.data.scenes[scene_name], depsgraph=depsgraph, build_cache=build_cache) + + scene = bpy.data.scenes[scene_name] + + try: + if bpy.context.window.scene != scene: + bpy.context.window.scene = scene + bpy.context.view_layer.update() + + scene_depsgraph = bpy.context.evaluated_depsgraph_get() + + LeenkxExporter.export_scene(bpy.context, asset_path, scene=scene, depsgraph=scene_depsgraph, build_cache=build_cache) + + finally: + if bpy.context.window.scene != initial_window_scene: + bpy.context.window.scene = initial_window_scene + bpy.context.view_layer.update() + if LeenkxExporter.export_physics: physics_found = True if LeenkxExporter.export_navigation: @@ -389,6 +416,12 @@ def export_data_impl(fp, sdk_path): line_deferred_dst = os.path.join(shaders_path, 'line_deferred.frag.glsl') shutil.copyfile(line_deferred_src, line_deferred_dst) + if rpdat.rp_renderer == 'Deferred': + # Copy deferred shader so that it can include compiled.inc + line_deferred_src = os.path.join(sdk_path, 'leenkx', 'Shaders', 'render_draw', 'render_line_deferred.frag.glsl') + line_deferred_dst = os.path.join(shaders_path, 'render_line_deferred.frag.glsl') + shutil.copyfile(line_deferred_src, line_deferred_dst) + for ref in assets.shader_passes: for s in assets.shader_passes_assets[ref]: assets.add_shader(shaders_path + '/' + s + '.glsl') @@ -947,6 +980,22 @@ def build_success(): log.clear() wrd = bpy.data.worlds['Lnx'] + sdk_path = lnx.utils.get_sdk_path() + target_name = lnx.utils.get_kha_target(state.target) + + files_path = os.path.join(lnx.utils.get_fp_build(), 'debug' if state.is_play else '', target_name) + + if target_name in ('html5', 'krom') and wrd.lnx_minify_js: + # Minify JS + minifier_path = sdk_path + '/lib/leenkx_tools/uglifyjs/bin/uglifyjs' + if target_name == 'html5': + jsfile = files_path + '/kha.js' + else: + jsfile = files_path + '/krom.js' + args = [lnx.utils.get_node_path(), minifier_path, jsfile, '-o', jsfile] + proc = subprocess.Popen(args) + proc.wait() + if state.is_play: cmd = [] width, height = lnx.utils.get_render_resolution(lnx.utils.get_active_scene()) @@ -1205,21 +1254,6 @@ def build_success(): webbrowser.open(url) elif state.is_publish: - sdk_path = lnx.utils.get_sdk_path() - target_name = lnx.utils.get_kha_target(state.target) - files_path = os.path.join(lnx.utils.get_fp_build(), target_name) - - if target_name in ('html5', 'krom') and wrd.lnx_minify_js: - # Minify JS - minifier_path = sdk_path + '/lib/leenkx_tools/uglifyjs/bin/uglifyjs' - if target_name == 'html5': - jsfile = files_path + '/kha.js' - else: - jsfile = files_path + '/krom.js' - args = [lnx.utils.get_node_path(), minifier_path, jsfile, '-o', jsfile] - proc = subprocess.Popen(args) - proc.wait() - if target_name == 'krom': # Copy Krom binaries if state.target == 'krom-windows': diff --git a/leenkx/blender/lnx/make_logic.py b/leenkx/blender/lnx/make_logic.py index da3ed787..552bcbe8 100644 --- a/leenkx/blender/lnx/make_logic.py +++ b/leenkx/blender/lnx/make_logic.py @@ -145,6 +145,11 @@ def build_node(node: bpy.types.Node, f: TextIO, name_prefix: Optional[str] = Non global parsed_nodes global parsed_ids + if node.mute: + if len(node.inputs) > 0 and node.inputs[0].is_linked: + return build_node(node.inputs[0].links[0].from_node, f, name_prefix) + return None + use_live_patch = lnx.utils.is_livepatch_enabled() link_group = False @@ -249,11 +254,16 @@ def build_node(node: bpy.types.Node, f: TextIO, name_prefix: Optional[str] = Non lnx.log.warn(f'Sockets do not match in logic node tree "{group_name}": node "{node.name}", socket "{inp.name}"') inp_name = build_node(n, f, name_prefix) - for i in range(0, len(n.outputs)): - if n.outputs[i] == socket: - inp_from = i - from_type = lnx.node_utils.get_socket_type(socket) - break + if inp_name is None: + inp_name = build_default_node(inp) + inp_from = 0 + from_type = lnx.node_utils.get_socket_type(inp) + else: + for i in range(0, len(n.outputs)): + if n.outputs[i] == socket: + inp_from = i + from_type = lnx.node_utils.get_socket_type(socket) + break # Not linked -> create node with default values else: @@ -307,7 +317,9 @@ def collect_nodes_from_output(out, f): reroutes.append(n) else: # immediatly add the current node - outputs.append(build_node(n, f)) + node_name = build_node(n, f) + if node_name is not None: + outputs.append(node_name) for reroute in reroutes: for o in reroute.outputs: outputs = outputs + collect_nodes_from_output(o, f) @@ -321,13 +333,9 @@ def get_root_nodes(node_group): return [] if node.type == 'FRAME': continue - linked = False - for out in node.outputs: - if out.is_linked: - linked = True - break - if not linked: # Assume node with no connected outputs as roots - roots.append(node) + if node.mute: + continue + roots.append(node) return roots def build_default_node(inp: bpy.types.NodeSocket): diff --git a/leenkx/blender/lnx/make_renderpath.py b/leenkx/blender/lnx/make_renderpath.py index 281796da..e049db20 100644 --- a/leenkx/blender/lnx/make_renderpath.py +++ b/leenkx/blender/lnx/make_renderpath.py @@ -159,6 +159,7 @@ def add_world_defs(): if not rpdat.rp_shadowmap_atlas: if point_lights == 1: wrd.world_defs += '_SinglePoint' + assets.add_khafile_def('lnx_single_point') elif point_lights > 1: wrd.world_defs += '_Clusters' assets.add_khafile_def('lnx_clusters') @@ -223,6 +224,9 @@ def build(): if rpdat.rp_renderer == 'Deferred': assets.add_shader_pass('copy_pass') + assets.add_shader_pass('blend_pass') + assets.add_shader_pass('add_pass') + if rpdat.rp_render_to_texture: assets.add_khafile_def('rp_render_to_texture') diff --git a/leenkx/blender/lnx/material/cycles.py b/leenkx/blender/lnx/material/cycles.py index db8c42b7..0867ab39 100644 --- a/leenkx/blender/lnx/material/cycles.py +++ b/leenkx/blender/lnx/material/cycles.py @@ -283,6 +283,11 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types. mat_state.emission_type = mat_state.EmissionType.NO_EMISSION + #for debugDraw color in deferred + if '_EmissionShaded' not in wrd.world_defs: + wrd.world_defs += '_EmissionShaded' + lnx.assets.add_khafile_def('rp_gbuffer_emission') + # Surface if parse_surface or parse_opacity: state.parents = [] @@ -559,6 +564,8 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: 'TEX_SKY', 'TEX_VORONOI', 'TEX_WAVE', + 'TEX_GABOR', + 'TEX_WHITE_NOISE', 'VERTEX_COLOR', 'BRIGHTCONTRAST', 'GAMMA', @@ -591,6 +598,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: 'COMBXYZ', 'VECT_MATH', 'DISPLACEMENT', + 'VECTOR_DISPLACEMENT', 'VECTOR_ROTATE', ) @@ -625,7 +633,7 @@ def get_value_input(node: bpy.types.Node, socket_names: Tuple[str, ...]) -> floa return '1.0' -def parse_normal_map_color_input(inp, strength_input=None): +def parse_normal_map_color_input(inp, strength_input=None, space='TANGENT'): frag = state.frag if state.basecol_only or not inp.is_linked or state.normal_parsed: @@ -633,20 +641,40 @@ def parse_normal_map_color_input(inp, strength_input=None): state.normal_parsed = True frag.write_normal += 1 - if not get_lnx_export_tangents() or mat_get_material().lnx_decal: # Compute TBN matrix - frag.write('vec3 texn = ({0}) * 2.0 - 1.0;'.format(parse_vector_input(inp))) - frag.write('texn.y = -texn.y;') - frag.add_include('std/normals.glsl') - frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);') - frag.write('n = TBN * normalize(texn);') - else: - frag.write('n = ({0}) * 2.0 - 1.0;'.format(parse_vector_input(inp))) - if strength_input is not None: - strength = parse_value_input(strength_input) + + color_val = parse_vector_input(inp) + strength = parse_value_input(strength_input) if strength_input is not None else '1.0' + + if space == 'TANGENT': + if not get_lnx_export_tangents() or mat_get_material().lnx_decal: + frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;') + frag.write('texn.y = -texn.y;') + frag.add_include('std/normals.glsl') + frag.write('mat3 TBN = cotangentFrame(n, -vVec, texCoord);') if strength != '1.0': - frag.write('n.xy *= {0};'.format(strength)) - frag.write('n = normalize(TBN * n);') - state.con.add_elem('tang', 'short4norm') + frag.write(f'texn.xy *= {strength};') + frag.write('n = TBN * normalize(texn);') + else: + frag.write(f'vec3 texn = ({color_val}) * 2.0 - 1.0;') + if strength != '1.0': + frag.write(f'texn.xy *= {strength};') + frag.write('n = normalize(TBN * texn);') + state.con.add_elem('tang', 'short4norm') + + elif space in ['OBJECT', 'BLENDER_OBJECT']: + frag.add_uniform('mat3 N', '_normalMatrix') + frag.write(f'vec3 objn = ({color_val}) * 2.0 - 1.0;') + if strength != '1.0': + frag.write(f'objn = mix(vec3(0.0, 0.0, 1.0), objn, {strength});') + frag.write('n = normalize(N * objn);') + + elif space in ['WORLD', 'BLENDER_WORLD']: + frag.write(f'vec3 worldn = ({color_val}) * 2.0 - 1.0;') + if strength != '1.0': + frag.write(f'n = normalize(mix(n, worldn, {strength}));') + else: + frag.write('n = normalize(worldn);') + frag.write_normal -= 1 @@ -700,6 +728,8 @@ def parse_value(node, socket): 'TEX_POINTDENSITY', 'TEX_VORONOI', 'TEX_WAVE', + 'TEX_GABOR', + 'TEX_WHITE_NOISE', 'LIGHT_FALLOFF', 'NORMAL', 'CLAMP', @@ -828,7 +858,6 @@ def write_result(link: bpy.types.NodeLink) -> Optional[str]: def write_procedurals(): if state.curshader not in state.procedurals_written: - state.curshader.add_function(c_functions.str_tex_proc) state.procedurals_written.add(state.curshader) @@ -869,14 +898,18 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex state.con.add_elem('tex', 'short2norm') curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link, default_value=default_value, is_lnx_mat_param=is_lnx_mat_param) triplanar = node.projection == 'BOX' + spherical = node.projection == 'SPHERE' + tube = node.projection == 'TUBE' if node.inputs[0].is_linked: uv_name = parse_vector_input(node.inputs[0]) if triplanar: - uv_name = 'vec3({0}.x, 1.0 - {0}.y, {0}.z)'.format(uv_name) + uv_name = 'vec3({0}.x, {0}.y, 1.0 - {0}.z)'.format(uv_name) + elif spherical or tube: + uv_name = 'vec3({0}.x, {0}.y, {0}.z)'.format(uv_name) else: uv_name = 'vec2({0}.x, 1.0 - {0}.y)'.format(uv_name) else: - uv_name = 'vec3(texCoord.xy, 0.0)' if triplanar else 'texCoord' + uv_name = 'vec3(texCoord.xy, 0.0)' if triplanar or spherical or tube else 'texCoord' if triplanar: if not curshader.has_include('std/mapping.glsl'): curshader.add_include('std/mapping.glsl') @@ -884,7 +917,16 @@ def texture_store(node, tex, tex_name, to_linear=False, unpremultiply=False, tex nor = 'TBN[2]' else: nor = 'n' - curshader.write('vec4 {0} = vec4(triplanarMapping({1}, {2}, {3}), 0.0);'.format(tex_store, tex_name, nor, uv_name)) + blend = getattr(node, 'projection_blend', 0.0) + curshader.write('vec4 {0} = boxProjection({1}, {2}, {3}, clamp({4}, 0.0, 1.0));'.format(tex_store, tex_name, nor, uv_name, blend)) + elif spherical: + if not curshader.has_include('std/mapping.glsl'): + curshader.add_include('std/mapping.glsl') + curshader.write('vec4 {0} = texture({1}, sphericalMapping({2}));'.format(tex_store, tex_name, uv_name)) + elif tube: + if not curshader.has_include('std/mapping.glsl'): + curshader.add_include('std/mapping.glsl') + curshader.write('vec4 {0} = texture({1}, tubeMapping({2}));'.format(tex_store, tex_name, uv_name)) else: if mat_state.texture_grad: curshader.write('vec4 {0} = textureGrad({1}, {2}.xy, g2.xy, g2.zw);'.format(tex_store, tex_name, uv_name)) diff --git a/leenkx/blender/lnx/material/cycles_functions.py b/leenkx/blender/lnx/material/cycles_functions.py index f3a4a57b..e02c0326 100644 --- a/leenkx/blender/lnx/material/cycles_functions.py +++ b/leenkx/blender/lnx/material/cycles_functions.py @@ -1,57 +1,3 @@ -str_tex_proc = """ -// Hash functions by Dave Hoskins -// -float hash_f(float p) { p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); } -float hash_f(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } -float hash_f(vec3 p3) { p3 = fract(p3 * 0.1031); p3 += dot(p3, p3.zyx + 31.32); return fract((p3.x + p3.y) * p3.z); } - -float noise(const vec3 x) { - const vec3 step = vec3(110, 241, 171); - - vec3 i = floor(x); - vec3 f = fract(x); - - // For performance, compute the base input to a 1D hash from the integer part of the argument and the - // incremental change to the 1D based on the 3D -> 1D wrapping - float n = dot(i, step); - - vec3 u = f * f * (3.0 - 2.0 * f); - return mix(mix(mix( hash_f(n + dot(step, vec3(0, 0, 0))), hash_f(n + dot(step, vec3(1, 0, 0))), u.x), - mix( hash_f(n + dot(step, vec3(0, 1, 0))), hash_f(n + dot(step, vec3(1, 1, 0))), u.x), u.y), - mix(mix( hash_f(n + dot(step, vec3(0, 0, 1))), hash_f(n + dot(step, vec3(1, 0, 1))), u.x), - mix( hash_f(n + dot(step, vec3(0, 1, 1))), hash_f(n + dot(step, vec3(1, 1, 1))), u.x), u.y), u.z); -} - -// Shader-code adapted from Blender -// https://github.com/sobotka/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_tex_wave.glsl & /gpu_shader_material_fractal_noise.glsl -float fractal_noise(const vec3 p, const float o) -{ - float fscale = 1.0; - float amp = 1.0; - float sum = 0.0; - float octaves = clamp(o, 0.0, 16.0); - int n = int(octaves); - for (int i = 0; i <= n; i++) { - float t = noise(fscale * p); - sum += t * amp; - amp *= 0.5; - fscale *= 2.0; - } - float rmd = octaves - floor(octaves); - if (rmd != 0.0) { - float t = noise(fscale * p); - float sum2 = sum + t * amp; - sum *= float(pow(2, n)) / float(pow(2, n + 1) - 1.0); - sum2 *= float(pow(2, n + 1)) / float(pow(2, n + 2) - 1); - return (1.0 - rmd) * sum + rmd * sum2; - } - else { - sum *= float(pow(2, n)) / float(pow(2, n + 1) - 1); - return sum; - } -} -""" - str_tex_checker = """ vec3 tex_checker(const vec3 co, const vec3 col1, const vec3 col2, const float scale) { // Prevent precision issues on unit coordinates @@ -71,52 +17,146 @@ float tex_checker_f(const vec3 co, const float scale) { } """ -str_tex_voronoi = """ -//Shader-code adapted from Blender -//https://github.com/sobotka/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_tex_voronoi.glsl -float voronoi_distance(const vec3 a, const vec3 b, const int metric, const float exponent) +str_tex_voronoi_5 = """ +#define SHD_VORONOI_EUCLIDEAN 0 +#define SHD_VORONOI_MANHATTAN 1 +#define SHD_VORONOI_CHEBYCHEV 2 +#define SHD_VORONOI_MINKOWSKI 3 + +#define SHD_VORONOI_F1 0 +#define SHD_VORONOI_F2 1 +#define SHD_VORONOI_SMOOTH_F1 2 +#define SHD_VORONOI_DISTANCE_TO_EDGE 3 +#define SHD_VORONOI_N_SPHERE_RADIUS 4 + +struct VoronoiParams { + float scale; + float detail; + float roughness; + float lacunarity; + float smoothness; + float exponent; + float randomness; + float max_distance; + bool normalize; + int feature; + int metric; +}; + +struct VoronoiOutput { + float Distance; + vec3 Color; + vec4 Position; +}; + +ivec3 hash_pcg3d_i(ivec3 v) { + v = v * 1664525 + 1013904223; + v.x += v.y * v.z; + v.y += v.z * v.x; + v.z += v.x * v.y; + v = v ^ (v >> 16); + v.x += v.y * v.z; + v.y += v.z * v.x; + v.z += v.x * v.y; + return v; +} + +vec3 hash_int3_to_vec3(ivec3 k) { + ivec3 h = hash_pcg3d_i(k); + return vec3(h & 0x7fffffff) * (1.0 / float(0x7fffffff)); +} + +ivec4 hash_pcg4d_i(ivec4 v) { + v = v * 1664525 + 1013904223; + v.x += v.y * v.w; + v.y += v.z * v.x; + v.z += v.x * v.y; + v.w += v.y * v.z; + v = v ^ (v >> 16); + v.x += v.y * v.w; + v.y += v.z * v.x; + v.z += v.x * v.y; + v.w += v.y * v.z; + return v; +} + +vec4 hash_int4_to_vec4(ivec4 k) { + ivec4 h = hash_pcg4d_i(k); + return vec4(h & 0x7fffffff) * (1.0 / float(0x7fffffff)); +} + +vec3 hash_int4_to_vec3(ivec4 k) { + return hash_int4_to_vec4(k).xyz; +} + +float voronoi_distance(vec3 a, vec3 b, VoronoiParams params) { - if (metric == 0) // SHD_VORONOI_EUCLIDEAN - { + if (params.metric == SHD_VORONOI_EUCLIDEAN) { return distance(a, b); } - else if (metric == 1) // SHD_VORONOI_MANHATTAN - { + else if (params.metric == SHD_VORONOI_MANHATTAN) { return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); } - else if (metric == 2) // SHD_VORONOI_CHEBYCHEV - { + else if (params.metric == SHD_VORONOI_CHEBYCHEV) { return max(abs(a.x - b.x), max(abs(a.y - b.y), abs(a.z - b.z))); } - else if (metric == 3) // SHD_VORONOI_MINKOWSKI - { - return pow(pow(abs(a.x - b.x), exponent) + pow(abs(a.y - b.y), exponent) + - pow(abs(a.z - b.z), exponent), - 1.0 / exponent); + else if (params.metric == SHD_VORONOI_MINKOWSKI) { + return pow(pow(abs(a.x - b.x), params.exponent) + pow(abs(a.y - b.y), params.exponent) + + pow(abs(a.z - b.z), params.exponent), + 1.0 / params.exponent); } else { - return 0.5; + return 0.0; } } -vec3 tex_voronoi(const vec3 coord, const float r, const int metric, const int outp, const float scale, const float exp) +float voronoi_distance(vec4 a, vec4 b, VoronoiParams params) { - float randomness = clamp(r, 0.0, 1.0); + if (params.metric == SHD_VORONOI_EUCLIDEAN) { + return distance(a, b); + } + else if (params.metric == SHD_VORONOI_MANHATTAN) { + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z) + abs(a.w - b.w); + } + else if (params.metric == SHD_VORONOI_CHEBYCHEV) { + return max(abs(a.x - b.x), max(abs(a.y - b.y), max(abs(a.z - b.z), abs(a.w - b.w)))); + } + else if (params.metric == SHD_VORONOI_MINKOWSKI) { + return pow(pow(abs(a.x - b.x), params.exponent) + pow(abs(a.y - b.y), params.exponent) + + pow(abs(a.z - b.z), params.exponent) + pow(abs(a.w - b.w), params.exponent), + 1.0 / params.exponent); + } + else { + return 0.0; + } +} - vec3 scaledCoord = coord * scale; - vec3 cellPosition = floor(scaledCoord); - vec3 localPosition = scaledCoord - cellPosition; +vec4 voronoi_position(vec3 coord) +{ + return vec4(coord.x, coord.y, coord.z, 0.0); +} + +vec4 voronoi_position(vec4 coord) +{ + return coord; +} + +VoronoiOutput voronoi_f1(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition_f = floor(coord); + vec3 localPosition = coord - cellPosition_f; + ivec3 cellPosition = ivec3(cellPosition_f); float minDistance = 8.0; - vec3 targetOffset, targetPosition; + ivec3 targetOffset = ivec3(0); + vec3 targetPosition = vec3(0.0); for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { - vec3 cellOffset = vec3(float(i), float(j), float(k)); - vec3 pointPosition = cellOffset; - if(randomness != 0.) { - pointPosition += vec3(hash_f(cellPosition+cellOffset), hash_f(cellPosition+cellOffset+972.37), hash_f(cellPosition+cellOffset+342.48)) * randomness;} - float distanceToPoint = voronoi_distance(pointPosition, localPosition, metric, exp); + ivec3 cellOffset = ivec3(i, j, k); + vec3 pointPosition = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); if (distanceToPoint < minDistance) { targetOffset = cellOffset; minDistance = distanceToPoint; @@ -125,35 +165,1928 @@ vec3 tex_voronoi(const vec3 coord, const float r, const int metric, const int ou } } } - if(outp == 0){return vec3(minDistance);} - else if(outp == 1) { - if(randomness == 0.) {return vec3(hash_f(cellPosition+targetOffset), hash_f(cellPosition+targetOffset+972.37), hash_f(cellPosition+targetOffset+342.48));} - return (targetPosition - targetOffset)/randomness; + + VoronoiOutput octave; + octave.Distance = minDistance; + octave.Color = hash_int3_to_vec3(cellPosition + targetOffset); + octave.Position = voronoi_position(targetPosition + cellPosition_f); + return octave; +} + +VoronoiOutput voronoi_f1(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition_f = floor(coord); + vec4 localPosition = coord - cellPosition_f; + ivec4 cellPosition = ivec4(cellPosition_f); + + float minDistance = 8.0; + ivec4 targetOffset = ivec4(0); + vec4 targetPosition = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 pointPosition = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < minDistance) { + targetOffset = cellOffset; + minDistance = distanceToPoint; + targetPosition = pointPosition; + } + } + } + } } - return (targetPosition + cellPosition) / scale; + + VoronoiOutput octave; + octave.Distance = minDistance; + octave.Color = hash_int4_to_vec3(cellPosition + targetOffset); + octave.Position = voronoi_position(targetPosition + cellPosition_f); + return octave; +} + +VoronoiOutput voronoi_smooth_f1(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition_f = floor(coord); + vec3 localPosition = coord - cellPosition_f; + ivec3 cellPosition = ivec3(cellPosition_f); + + float smoothDistance = 0.0; + vec3 smoothColor = vec3(0.0); + vec3 smoothPosition = vec3(0.0); + float h = -1.0; + for (int k = -2; k <= 2; k++) { + for (int j = -2; j <= 2; j++) { + for (int i = -2; i <= 2; i++) { + ivec3 cellOffset = ivec3(i, j, k); + vec3 pointPosition = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + h = h == -1.0 ? + 1.0 : + smoothstep(0.0, + 1.0, + 0.5 + 0.5 * (smoothDistance - distanceToPoint) / params.smoothness); + float correctionFactor = params.smoothness * h * (1.0 - h); + smoothDistance = mix(smoothDistance, distanceToPoint, h) - correctionFactor; + correctionFactor /= 1.0 + 3.0 * params.smoothness; + vec3 cellColor = hash_int3_to_vec3(cellPosition + cellOffset); + smoothColor = mix(smoothColor, cellColor, h) - correctionFactor; + smoothPosition = mix(smoothPosition, pointPosition, h) - correctionFactor; + } + } + } + + VoronoiOutput octave; + octave.Distance = smoothDistance; + octave.Color = smoothColor; + octave.Position = voronoi_position(cellPosition_f + smoothPosition); + return octave; +} + +VoronoiOutput voronoi_smooth_f1(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition_f = floor(coord); + vec4 localPosition = coord - cellPosition_f; + ivec4 cellPosition = ivec4(cellPosition_f); + + float smoothDistance = 0.0; + vec3 smoothColor = vec3(0.0); + vec4 smoothPosition = vec4(0.0); + float h = -1.0; + for (int u = -2; u <= 2; u++) { + for (int k = -2; k <= 2; k++) { + for (int j = -2; j <= 2; j++) { + for (int i = -2; i <= 2; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 pointPosition = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + h = h == -1.0 ? + 1.0 : + smoothstep(0.0, + 1.0, + 0.5 + 0.5 * (smoothDistance - distanceToPoint) / params.smoothness); + float correctionFactor = params.smoothness * h * (1.0 - h); + smoothDistance = mix(smoothDistance, distanceToPoint, h) - correctionFactor; + correctionFactor /= 1.0 + 3.0 * params.smoothness; + vec3 cellColor = hash_int4_to_vec3(cellPosition + cellOffset); + smoothColor = mix(smoothColor, cellColor, h) - correctionFactor; + smoothPosition = mix(smoothPosition, pointPosition, h) - correctionFactor; + } + } + } + } + + VoronoiOutput octave; + octave.Distance = smoothDistance; + octave.Color = smoothColor; + octave.Position = voronoi_position(cellPosition_f + smoothPosition); + return octave; +} + +VoronoiOutput voronoi_f2(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition_f = floor(coord); + vec3 localPosition = coord - cellPosition_f; + ivec3 cellPosition = ivec3(cellPosition_f); + + float distanceF1 = 8.0; + float distanceF2 = 8.0; + ivec3 offsetF1 = ivec3(0); + vec3 positionF1 = vec3(0.0); + ivec3 offsetF2 = ivec3(0); + vec3 positionF2 = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec3 cellOffset = ivec3(i, j, k); + vec3 pointPosition = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < distanceF1) { + distanceF2 = distanceF1; + distanceF1 = distanceToPoint; + offsetF2 = offsetF1; + offsetF1 = cellOffset; + positionF2 = positionF1; + positionF1 = pointPosition; + } + else if (distanceToPoint < distanceF2) { + distanceF2 = distanceToPoint; + offsetF2 = cellOffset; + positionF2 = pointPosition; + } + } + } + } + + VoronoiOutput octave; + octave.Distance = distanceF2; + octave.Color = hash_int3_to_vec3(cellPosition + offsetF2); + octave.Position = voronoi_position(positionF2 + cellPosition_f); + return octave; +} + +VoronoiOutput voronoi_f2(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition_f = floor(coord); + vec4 localPosition = coord - cellPosition_f; + ivec4 cellPosition = ivec4(cellPosition_f); + + float distanceF1 = 8.0; + float distanceF2 = 8.0; + ivec4 offsetF1 = ivec4(0); + vec4 positionF1 = vec4(0.0); + ivec4 offsetF2 = ivec4(0); + vec4 positionF2 = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 pointPosition = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < distanceF1) { + distanceF2 = distanceF1; + distanceF1 = distanceToPoint; + offsetF2 = offsetF1; + offsetF1 = cellOffset; + positionF2 = positionF1; + positionF1 = pointPosition; + } + else if (distanceToPoint < distanceF2) { + distanceF2 = distanceToPoint; + offsetF2 = cellOffset; + positionF2 = pointPosition; + } + } + } + } + } + + VoronoiOutput octave; + octave.Distance = distanceF2; + octave.Color = hash_int4_to_vec3(cellPosition + offsetF2); + octave.Position = voronoi_position(positionF2 + cellPosition_f); + return octave; +} + +float voronoi_distance_to_edge(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition_f = floor(coord); + vec3 localPosition = coord - cellPosition_f; + ivec3 cellPosition = ivec3(cellPosition_f); + + vec3 vectorToClosest = vec3(0.0); + float minDistance = 8.0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec3 cellOffset = ivec3(i, j, k); + vec3 vectorToPoint = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness - + localPosition; + float distanceToPoint = dot(vectorToPoint, vectorToPoint); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + vectorToClosest = vectorToPoint; + } + } + } + } + + minDistance = 8.0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec3 cellOffset = ivec3(i, j, k); + vec3 vectorToPoint = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness - + localPosition; + vec3 perpendicularToEdge = vectorToPoint - vectorToClosest; + if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001) { + float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0, + normalize(perpendicularToEdge)); + minDistance = min(minDistance, distanceToEdge); + } + } + } + } + + return minDistance; +} + +float voronoi_distance_to_edge(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition_f = floor(coord); + vec4 localPosition = coord - cellPosition_f; + ivec4 cellPosition = ivec4(cellPosition_f); + + vec4 vectorToClosest = vec4(0.0); + float minDistance = 8.0; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 vectorToPoint = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness - + localPosition; + float distanceToPoint = dot(vectorToPoint, vectorToPoint); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + vectorToClosest = vectorToPoint; + } + } + } + } + } + + minDistance = 8.0; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 vectorToPoint = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness - + localPosition; + vec4 perpendicularToEdge = vectorToPoint - vectorToClosest; + if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001) { + float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0, + normalize(perpendicularToEdge)); + minDistance = min(minDistance, distanceToEdge); + } + } + } + } + } + + return minDistance; +} + +float voronoi_n_sphere_radius(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition_f = floor(coord); + vec3 localPosition = coord - cellPosition_f; + ivec3 cellPosition = ivec3(cellPosition_f); + + vec3 closestPoint = vec3(0.0); + ivec3 closestPointOffset = ivec3(0); + float minDistance = 8.0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec3 cellOffset = ivec3(i, j, k); + vec3 pointPosition = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(pointPosition, localPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPoint = pointPosition; + closestPointOffset = cellOffset; + } + } + } + } + + minDistance = 8.0; + vec3 closestPointToClosestPoint = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + if (i == 0 && j == 0 && k == 0) { + continue; + } + ivec3 cellOffset = ivec3(i, j, k) + closestPointOffset; + vec3 pointPosition = vec3(cellOffset) + + hash_int3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(closestPoint, pointPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPointToClosestPoint = pointPosition; + } + } + } + } + + return distance(closestPointToClosestPoint, closestPoint) / 2.0; +} + +float voronoi_n_sphere_radius(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition_f = floor(coord); + vec4 localPosition = coord - cellPosition_f; + ivec4 cellPosition = ivec4(cellPosition_f); + + vec4 closestPoint = vec4(0.0); + ivec4 closestPointOffset = ivec4(0); + float minDistance = 8.0; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + ivec4 cellOffset = ivec4(i, j, k, u); + vec4 pointPosition = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(pointPosition, localPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPoint = pointPosition; + closestPointOffset = cellOffset; + } + } + } + } + } + + minDistance = 8.0; + vec4 closestPointToClosestPoint = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + if (i == 0 && j == 0 && k == 0 && u == 0) { + continue; + } + ivec4 cellOffset = ivec4(i, j, k, u) + closestPointOffset; + vec4 pointPosition = vec4(cellOffset) + + hash_int4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(closestPoint, pointPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPointToClosestPoint = pointPosition; + } + } + } + } + } + + return distance(closestPointToClosestPoint, closestPoint) / 2.0; +} + +float fractal_voronoi_distance_to_edge(VoronoiParams params, vec3 coord) +{ + float amplitude = 1.0; + float max_amplitude = params.max_distance; + float scale = 1.0; + float dist = 8.0; + + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + float octave_distance = voronoi_distance_to_edge(params, coord * scale); + + if (zero_input) { + dist = octave_distance; + break; + } + else if (float(i) <= params.detail) { + max_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + dist = mix(dist, min(dist, octave_distance / scale), amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + float lerp_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + max_amplitude = mix(max_amplitude, lerp_amplitude, remainder); + float lerp_distance = mix(dist, min(dist, octave_distance / scale), amplitude); + dist = mix(dist, min(dist, lerp_distance), remainder); + } + } + } + + if (params.normalize) { + dist /= max_amplitude; + } + + return dist; +} + +float fractal_voronoi_distance_to_edge(VoronoiParams params, vec4 coord) +{ + float amplitude = 1.0; + float max_amplitude = params.max_distance; + float scale = 1.0; + float dist = 8.0; + + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + float octave_distance = voronoi_distance_to_edge(params, coord * scale); + + if (zero_input) { + dist = octave_distance; + break; + } + else if (float(i) <= params.detail) { + max_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + dist = mix(dist, min(dist, octave_distance / scale), amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + float lerp_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + max_amplitude = mix(max_amplitude, lerp_amplitude, remainder); + float lerp_distance = mix(dist, min(dist, octave_distance / scale), amplitude); + dist = mix(dist, min(dist, lerp_distance), remainder); + } + } + } + + if (params.normalize) { + dist /= max_amplitude; + } + + return dist; +} + +VoronoiOutput fractal_voronoi_3d(VoronoiParams params, vec3 coord) +{ + float amplitude = 1.0; + float max_amplitude = 0.0; + float scale = 1.0; + + VoronoiOutput Output; + Output.Distance = 0.0; + Output.Color = vec3(0.0, 0.0, 0.0); + Output.Position = vec4(0.0, 0.0, 0.0, 0.0); + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + VoronoiOutput octave; + if (params.feature == SHD_VORONOI_F2) { + octave = voronoi_f2(params, coord * scale); + } + else if (params.feature == SHD_VORONOI_SMOOTH_F1 && params.smoothness != 0.0) { + octave = voronoi_smooth_f1(params, coord * scale); + } + else { + octave = voronoi_f1(params, coord * scale); + } + + if (zero_input) { + max_amplitude = 1.0; + Output = octave; + break; + } + else if (float(i) <= params.detail) { + max_amplitude += amplitude; + Output.Distance += octave.Distance * amplitude; + Output.Color += octave.Color * amplitude; + Output.Position = mix(Output.Position, octave.Position / scale, amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + max_amplitude = mix(max_amplitude, max_amplitude + amplitude, remainder); + Output.Distance = mix( + Output.Distance, Output.Distance + octave.Distance * amplitude, remainder); + Output.Color = mix(Output.Color, Output.Color + octave.Color * amplitude, remainder); + Output.Position = mix( + Output.Position, mix(Output.Position, octave.Position / scale, amplitude), remainder); + } + } + } + + if (params.normalize) { + Output.Distance /= max_amplitude * params.max_distance; + Output.Color /= max_amplitude; + } + + Output.Position = Output.Position / params.scale; + + return Output; +} + +VoronoiOutput fractal_voronoi_4d(VoronoiParams params, vec4 coord) +{ + float amplitude = 1.0; + float max_amplitude = 0.0; + float scale = 1.0; + + VoronoiOutput Output; + Output.Distance = 0.0; + Output.Color = vec3(0.0, 0.0, 0.0); + Output.Position = vec4(0.0, 0.0, 0.0, 0.0); + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + VoronoiOutput octave; + if (params.feature == SHD_VORONOI_F2) { + octave = voronoi_f2(params, coord * scale); + } + else if (params.feature == SHD_VORONOI_SMOOTH_F1 && params.smoothness != 0.0) { + octave = voronoi_smooth_f1(params, coord * scale); + } + else { + octave = voronoi_f1(params, coord * scale); + } + + if (zero_input) { + max_amplitude = 1.0; + Output = octave; + break; + } + else if (float(i) <= params.detail) { + max_amplitude += amplitude; + Output.Distance += octave.Distance * amplitude; + Output.Color += octave.Color * amplitude; + Output.Position = mix(Output.Position, octave.Position / scale, amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + max_amplitude = mix(max_amplitude, max_amplitude + amplitude, remainder); + Output.Distance = mix( + Output.Distance, Output.Distance + octave.Distance * amplitude, remainder); + Output.Color = mix(Output.Color, Output.Color + octave.Color * amplitude, remainder); + Output.Position = mix( + Output.Position, mix(Output.Position, octave.Position / scale, amplitude), remainder); + } + } + } + + if (params.normalize) { + Output.Distance /= max_amplitude * params.max_distance; + Output.Color /= max_amplitude; + } + + Output.Position = Output.Position / params.scale; + + return Output; +} + +vec3 tex_voronoi_3d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) +{ + VoronoiParams params; + params.feature = feature; + params.metric = metric; + params.scale = scale; + params.detail = clamp(detail, 0.0, 15.0); + params.roughness = clamp(roughness, 0.0, 1.0); + params.lacunarity = lacunarity; + params.smoothness = clamp(smoothness / 2.0, 0.0, 0.5); + params.exponent = exp; + params.randomness = clamp(randomness, 0.0, 1.0); + + if (feature == SHD_VORONOI_F2) { + params.max_distance = (0.5 + 0.5 * params.randomness) * 2.0; + } else { + params.max_distance = 0.5 + 0.5 * params.randomness; + } + + params.normalize = normalize == 1; + + vec3 scaledCoord = coord * scale; + + if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + float dist = fractal_voronoi_distance_to_edge(params, scaledCoord); + if (outp == 0) return vec3(dist); + return vec3(0.0); + } + else if (feature == SHD_VORONOI_N_SPHERE_RADIUS) { + float radius = voronoi_n_sphere_radius(params, scaledCoord); + if (outp == 0) return vec3(radius); + return vec3(0.0); + } + else { + VoronoiOutput Output = fractal_voronoi_3d(params, scaledCoord); + if (outp == 0) return vec3(Output.Distance); + else if (outp == 1) return Output.Color; + return Output.Position.xyz; + } +} + +vec3 tex_voronoi_4d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) +{ + VoronoiParams params; + params.feature = feature; + params.metric = metric; + params.scale = scale; + params.detail = clamp(detail, 0.0, 15.0); + params.roughness = clamp(roughness, 0.0, 1.0); + params.lacunarity = lacunarity; + params.smoothness = clamp(smoothness / 2.0, 0.0, 0.5); + params.exponent = exp; + params.randomness = clamp(randomness, 0.0, 1.0); + + if (feature == SHD_VORONOI_F2) { + params.max_distance = (0.5 + 0.5 * params.randomness) * 2.0; + } else { + params.max_distance = 0.5 + 0.5 * params.randomness; + } + + params.normalize = normalize == 1; + + vec4 scaledCoord = vec4(coord * scale, w * scale); + + if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + float dist = fractal_voronoi_distance_to_edge(params, scaledCoord); + if (outp == 0) return vec3(dist); + return vec3(0.0); + } + else if (feature == SHD_VORONOI_N_SPHERE_RADIUS) { + float radius = voronoi_n_sphere_radius(params, scaledCoord); + if (outp == 0) return vec3(radius); + return vec3(0.0); + } + else { + VoronoiOutput Output = fractal_voronoi_4d(params, scaledCoord); + if (outp == 0) return vec3(Output.Distance); + else if (outp == 1) return Output.Color; + else if (outp == 2) return Output.Position.xyz; + return vec3(Output.Position.w); + } +} + +vec3 tex_voronoi_1d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) { + return tex_voronoi_3d(vec3(w, 0.0, 0.0), randomness, metric, outp, scale, exp, 0.0, detail, roughness, lacunarity, smoothness, feature, normalize); +} + +vec3 tex_voronoi_2d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) { + return tex_voronoi_3d(vec3(coord.x, coord.y, 0.0), randomness, metric, outp, scale, exp, 0.0, detail, roughness, lacunarity, smoothness, feature, normalize); } """ -# Based on https://www.shadertoy.com/view/4sfGzS -# Copyright © 2013 Inigo Quilez -# The MIT License - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# float tex_noise_f(const vec3 x) { -# vec3 p = floor(x); -# vec3 f = fract(x); -# f = f * f * (3.0 - 2.0 * f); -# vec2 uv = (p.xy + vec2(37.0, 17.0) * p.z) + f.xy; -# vec2 rg = texture(snoise256, (uv + 0.5) / 256.0).yx; -# return mix(rg.x, rg.y, f.z); -# } -# By Morgan McGuire @morgan3d, http://graphicscodex.com Reuse permitted under the BSD license. -# https://www.shadertoy.com/view/4dS3Wd -str_tex_noise = """ -float tex_noise(const vec3 p, const float detail, const float distortion) { - vec3 pk = p; - if (distortion != 0.0) { - pk += vec3(noise(p) * distortion); +str_tex_voronoi_4 = """ +#define SHD_VORONOI_EUCLIDEAN 0 +#define SHD_VORONOI_MANHATTAN 1 +#define SHD_VORONOI_CHEBYCHEV 2 +#define SHD_VORONOI_MINKOWSKI 3 + +#define SHD_VORONOI_F1 0 +#define SHD_VORONOI_F2 1 +#define SHD_VORONOI_SMOOTH_F1 2 +#define SHD_VORONOI_DISTANCE_TO_EDGE 3 +#define SHD_VORONOI_N_SPHERE_RADIUS 4 + +#define FLT_MAX 3.402823466e+38 + +vec4 safe_divide(vec4 a, float b) +{ + return (b != 0.0) ? a / b : vec4(0.0); +} + +struct VoronoiParams { + float scale; + float detail; + float roughness; + float lacunarity; + float smoothness; + float exponent; + float randomness; + float max_distance; + bool normalize; + int feature; + int metric; +}; + +struct VoronoiOutput { + float Distance; + vec3 Color; + vec4 Position; +}; + +#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) + +#define mix_hash(a, b, c) \ + { \ + a -= c; \ + a ^= rot(c, 4); \ + c += b; \ + b -= a; \ + b ^= rot(a, 6); \ + a += c; \ + c -= b; \ + c ^= rot(b, 8); \ + b += a; \ + a -= c; \ + a ^= rot(c, 16); \ + c += b; \ + b -= a; \ + b ^= rot(a, 19); \ + a += c; \ + c -= b; \ + c ^= rot(b, 4); \ + b += a; \ } - return fractal_noise(pk, detail); + +#define final_hash(a, b, c) \ + { \ + c ^= b; \ + c -= rot(b, 14); \ + a ^= c; \ + a -= rot(c, 11); \ + b ^= a; \ + b -= rot(a, 25); \ + c ^= b; \ + c -= rot(b, 16); \ + a ^= c; \ + a -= rot(c, 4); \ + b ^= a; \ + b -= rot(a, 14); \ + c ^= b; \ + c -= rot(b, 24); \ + } + +uint v_hash_uint3(uint kx, uint ky, uint kz) +{ + uint a, b, c; + a = b = c = 0xdeadbeefu + (3u << 2u) + 13u; + + c += kz; + b += ky; + a += kx; + final_hash(a, b, c); + + return c; +} + +uint v_hash_uint4(uint kx, uint ky, uint kz, uint kw) +{ + uint a, b, c; + a = b = c = 0xdeadbeefu + (4u << 2u) + 13u; + + a += kx; + b += ky; + c += kz; + mix_hash(a, b, c); + + a += kw; + final_hash(a, b, c); + + return c; +} + +#undef rot +#undef final_hash +#undef mix_hash + +float v_hash_uint3_to_float(uint kx, uint ky, uint kz) +{ + return float(v_hash_uint3(kx, ky, kz)) / float(0xFFFFFFFFu); +} + +float v_hash_uint4_to_float(uint kx, uint ky, uint kz, uint kw) +{ + return float(v_hash_uint4(kx, ky, kz, kw)) / float(0xFFFFFFFFu); +} + +float v_hash_vec3_to_float(vec3 k) +{ + return v_hash_uint3_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z)); +} + +float v_hash_vec4_to_float(vec4 k) +{ + return v_hash_uint4_to_float( + floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z), floatBitsToUint(k.w)); +} + +vec3 v_hash_vec3_to_vec3(vec3 k) +{ + return vec3( + v_hash_vec3_to_float(k), v_hash_vec4_to_float(vec4(k, 1.0)), v_hash_vec4_to_float(vec4(k, 2.0))); +} + +vec4 v_hash_vec4_to_vec4(vec4 k) +{ + return vec4(v_hash_vec4_to_float(k.xyzw), + v_hash_vec4_to_float(k.wxyz), + v_hash_vec4_to_float(k.zwxy), + v_hash_vec4_to_float(k.yzwx)); +} + +vec3 v_hash_vec4_to_vec3(vec4 k) +{ + return vec3(v_hash_vec4_to_float(k.xyzw), v_hash_vec4_to_float(k.zxwy), v_hash_vec4_to_float(k.wzyx)); +} + +float voronoi_distance(vec3 a, vec3 b, VoronoiParams params) +{ + if (params.metric == SHD_VORONOI_EUCLIDEAN) { + return distance(a, b); + } + else if (params.metric == SHD_VORONOI_MANHATTAN) { + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); + } + else if (params.metric == SHD_VORONOI_CHEBYCHEV) { + return max(abs(a.x - b.x), max(abs(a.y - b.y), abs(a.z - b.z))); + } + else if (params.metric == SHD_VORONOI_MINKOWSKI) { + return pow(pow(abs(a.x - b.x), params.exponent) + pow(abs(a.y - b.y), params.exponent) + + pow(abs(a.z - b.z), params.exponent), + 1.0 / params.exponent); + } + else { + return 0.0; + } +} + +float voronoi_distance(vec4 a, vec4 b, VoronoiParams params) +{ + if (params.metric == SHD_VORONOI_EUCLIDEAN) { + return distance(a, b); + } + else if (params.metric == SHD_VORONOI_MANHATTAN) { + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z) + abs(a.w - b.w); + } + else if (params.metric == SHD_VORONOI_CHEBYCHEV) { + return max(abs(a.x - b.x), max(abs(a.y - b.y), max(abs(a.z - b.z), abs(a.w - b.w)))); + } + else if (params.metric == SHD_VORONOI_MINKOWSKI) { + return pow(pow(abs(a.x - b.x), params.exponent) + pow(abs(a.y - b.y), params.exponent) + + pow(abs(a.z - b.z), params.exponent) + pow(abs(a.w - b.w), params.exponent), + 1.0 / params.exponent); + } + else { + return 0.0; + } +} + +vec4 voronoi_position(vec3 coord) +{ + return vec4(coord.x, coord.y, coord.z, 0.0); +} + +vec4 voronoi_position(vec4 coord) +{ + return coord; +} + +VoronoiOutput voronoi_f1(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition = floor(coord); + vec3 localPosition = coord - cellPosition; + + float minDistance = FLT_MAX; + vec3 targetOffset = vec3(0.0); + vec3 targetPosition = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 pointPosition = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < minDistance) { + targetOffset = cellOffset; + minDistance = distanceToPoint; + targetPosition = pointPosition; + } + } + } + } + + VoronoiOutput octave; + octave.Distance = minDistance; + octave.Color = v_hash_vec3_to_vec3(cellPosition + targetOffset); + octave.Position = voronoi_position(targetPosition + cellPosition); + return octave; +} + +VoronoiOutput voronoi_f1(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition = floor(coord); + vec4 localPosition = coord - cellPosition; + + float minDistance = FLT_MAX; + vec4 targetOffset = vec4(0.0); + vec4 targetPosition = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 pointPosition = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < minDistance) { + targetOffset = cellOffset; + minDistance = distanceToPoint; + targetPosition = pointPosition; + } + } + } + } + } + + VoronoiOutput octave; + octave.Distance = minDistance; + octave.Color = v_hash_vec4_to_vec3(cellPosition + targetOffset); + octave.Position = voronoi_position(targetPosition + cellPosition); + return octave; +} + +VoronoiOutput voronoi_smooth_f1(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition = floor(coord); + vec3 localPosition = coord - cellPosition; + + float smoothDistance = 0.0; + vec3 smoothColor = vec3(0.0); + vec3 smoothPosition = vec3(0.0); + float h = -1.0; + for (int k = -2; k <= 2; k++) { + for (int j = -2; j <= 2; j++) { + for (int i = -2; i <= 2; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 pointPosition = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + h = h == -1.0 ? + 1.0 : + smoothstep( + 0.0, 1.0, 0.5 + 0.5 * (smoothDistance - distanceToPoint) / params.smoothness); + float correctionFactor = params.smoothness * h * (1.0 - h); + smoothDistance = mix(smoothDistance, distanceToPoint, h) - correctionFactor; + correctionFactor /= 1.0 + 3.0 * params.smoothness; + vec3 cellColor = v_hash_vec3_to_vec3(cellPosition + cellOffset); + smoothColor = mix(smoothColor, cellColor, h) - correctionFactor; + smoothPosition = mix(smoothPosition, pointPosition, h) - correctionFactor; + } + } + } + + VoronoiOutput octave; + octave.Distance = smoothDistance; + octave.Color = smoothColor; + octave.Position = voronoi_position(cellPosition + smoothPosition); + return octave; +} + +VoronoiOutput voronoi_smooth_f1(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition = floor(coord); + vec4 localPosition = coord - cellPosition; + + float smoothDistance = 0.0; + vec3 smoothColor = vec3(0.0); + vec4 smoothPosition = vec4(0.0); + float h = -1.0; + for (int u = -2; u <= 2; u++) { + for (int k = -2; k <= 2; k++) { + for (int j = -2; j <= 2; j++) { + for (int i = -2; i <= 2; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 pointPosition = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + h = h == -1.0 ? + 1.0 : + smoothstep(0.0, + 1.0, + 0.5 + 0.5 * (smoothDistance - distanceToPoint) / params.smoothness); + float correctionFactor = params.smoothness * h * (1.0 - h); + smoothDistance = mix(smoothDistance, distanceToPoint, h) - correctionFactor; + correctionFactor /= 1.0 + 3.0 * params.smoothness; + vec3 cellColor = v_hash_vec4_to_vec3(cellPosition + cellOffset); + smoothColor = mix(smoothColor, cellColor, h) - correctionFactor; + smoothPosition = mix(smoothPosition, pointPosition, h) - correctionFactor; + } + } + } + } + + VoronoiOutput octave; + octave.Distance = smoothDistance; + octave.Color = smoothColor; + octave.Position = voronoi_position(cellPosition + smoothPosition); + return octave; +} + +VoronoiOutput voronoi_f2(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition = floor(coord); + vec3 localPosition = coord - cellPosition; + + float distanceF1 = FLT_MAX; + float distanceF2 = FLT_MAX; + vec3 offsetF1 = vec3(0.0); + vec3 positionF1 = vec3(0.0); + vec3 offsetF2 = vec3(0.0); + vec3 positionF2 = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 pointPosition = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < distanceF1) { + distanceF2 = distanceF1; + distanceF1 = distanceToPoint; + offsetF2 = offsetF1; + offsetF1 = cellOffset; + positionF2 = positionF1; + positionF1 = pointPosition; + } + else if (distanceToPoint < distanceF2) { + distanceF2 = distanceToPoint; + offsetF2 = cellOffset; + positionF2 = pointPosition; + } + } + } + } + + VoronoiOutput octave; + octave.Distance = distanceF2; + octave.Color = v_hash_vec3_to_vec3(cellPosition + offsetF2); + octave.Position = voronoi_position(positionF2 + cellPosition); + return octave; +} + +VoronoiOutput voronoi_f2(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition = floor(coord); + vec4 localPosition = coord - cellPosition; + + float distanceF1 = FLT_MAX; + float distanceF2 = FLT_MAX; + vec4 offsetF1 = vec4(0.0); + vec4 positionF1 = vec4(0.0); + vec4 offsetF2 = vec4(0.0); + vec4 positionF2 = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 pointPosition = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = voronoi_distance(pointPosition, localPosition, params); + if (distanceToPoint < distanceF1) { + distanceF2 = distanceF1; + distanceF1 = distanceToPoint; + offsetF2 = offsetF1; + offsetF1 = cellOffset; + positionF2 = positionF1; + positionF1 = pointPosition; + } + else if (distanceToPoint < distanceF2) { + distanceF2 = distanceToPoint; + offsetF2 = cellOffset; + positionF2 = pointPosition; + } + } + } + } + } + + VoronoiOutput octave; + octave.Distance = distanceF2; + octave.Color = v_hash_vec4_to_vec3(cellPosition + offsetF2); + octave.Position = voronoi_position(positionF2 + cellPosition); + return octave; +} + +float voronoi_distance_to_edge(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition = floor(coord); + vec3 localPosition = coord - cellPosition; + + vec3 vectorToClosest = vec3(0.0); + float minDistance = FLT_MAX; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 vectorToPoint = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness - + localPosition; + float distanceToPoint = dot(vectorToPoint, vectorToPoint); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + vectorToClosest = vectorToPoint; + } + } + } + } + + minDistance = FLT_MAX; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 vectorToPoint = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness - + localPosition; + vec3 perpendicularToEdge = vectorToPoint - vectorToClosest; + if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001) { + float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0, + normalize(perpendicularToEdge)); + minDistance = min(minDistance, distanceToEdge); + } + } + } + } + + return minDistance; +} + +float voronoi_distance_to_edge(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition = floor(coord); + vec4 localPosition = coord - cellPosition; + + vec4 vectorToClosest = vec4(0.0); + float minDistance = FLT_MAX; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 vectorToPoint = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness - + localPosition; + float distanceToPoint = dot(vectorToPoint, vectorToPoint); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + vectorToClosest = vectorToPoint; + } + } + } + } + } + + minDistance = FLT_MAX; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 vectorToPoint = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness - + localPosition; + vec4 perpendicularToEdge = vectorToPoint - vectorToClosest; + if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001) { + float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0, + normalize(perpendicularToEdge)); + minDistance = min(minDistance, distanceToEdge); + } + } + } + } + } + + return minDistance; +} + +float voronoi_n_sphere_radius(VoronoiParams params, vec3 coord) +{ + vec3 cellPosition = floor(coord); + vec3 localPosition = coord - cellPosition; + + vec3 closestPoint = vec3(0.0); + vec3 closestPointOffset = vec3(0.0); + float minDistance = FLT_MAX; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cellOffset = vec3(i, j, k); + vec3 pointPosition = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(pointPosition, localPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPoint = pointPosition; + closestPointOffset = cellOffset; + } + } + } + } + + minDistance = FLT_MAX; + vec3 closestPointToClosestPoint = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + if (i == 0 && j == 0 && k == 0) { + continue; + } + vec3 cellOffset = vec3(i, j, k) + closestPointOffset; + vec3 pointPosition = cellOffset + + v_hash_vec3_to_vec3(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(closestPoint, pointPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPointToClosestPoint = pointPosition; + } + } + } + } + + return distance(closestPointToClosestPoint, closestPoint) / 2.0; +} + +float voronoi_n_sphere_radius(VoronoiParams params, vec4 coord) +{ + vec4 cellPosition = floor(coord); + vec4 localPosition = coord - cellPosition; + + vec4 closestPoint = vec4(0.0); + vec4 closestPointOffset = vec4(0.0); + float minDistance = FLT_MAX; + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec4 cellOffset = vec4(i, j, k, u); + vec4 pointPosition = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(pointPosition, localPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPoint = pointPosition; + closestPointOffset = cellOffset; + } + } + } + } + } + + minDistance = FLT_MAX; + vec4 closestPointToClosestPoint = vec4(0.0); + for (int u = -1; u <= 1; u++) { + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + if (i == 0 && j == 0 && k == 0 && u == 0) { + continue; + } + vec4 cellOffset = vec4(i, j, k, u) + closestPointOffset; + vec4 pointPosition = cellOffset + + v_hash_vec4_to_vec4(cellPosition + cellOffset) * params.randomness; + float distanceToPoint = distance(closestPoint, pointPosition); + if (distanceToPoint < minDistance) { + minDistance = distanceToPoint; + closestPointToClosestPoint = pointPosition; + } + } + } + } + } + + return distance(closestPointToClosestPoint, closestPoint) / 2.0; +} + +float fractal_voronoi_distance_to_edge(VoronoiParams params, vec3 coord) +{ + float amplitude = 1.0; + float max_amplitude = params.max_distance; + float scale = 1.0; + float dist = 8.0; + + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + float octave_distance = voronoi_distance_to_edge(params, coord * scale); + + if (zero_input) { + dist = octave_distance; + break; + } + else if (float(i) <= params.detail) { + max_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + dist = mix(dist, min(dist, octave_distance / scale), amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + float lerp_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + max_amplitude = mix(max_amplitude, lerp_amplitude, remainder); + float lerp_distance = mix(dist, min(dist, octave_distance / scale), amplitude); + dist = mix(dist, min(dist, lerp_distance), remainder); + } + } + } + + if (params.normalize) { + dist /= max_amplitude; + } + + return dist; +} + +float fractal_voronoi_distance_to_edge(VoronoiParams params, vec4 coord) +{ + float amplitude = 1.0; + float max_amplitude = params.max_distance; + float scale = 1.0; + float dist = 8.0; + + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + float octave_distance = voronoi_distance_to_edge(params, coord * scale); + + if (zero_input) { + dist = octave_distance; + break; + } + else if (float(i) <= params.detail) { + max_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + dist = mix(dist, min(dist, octave_distance / scale), amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + float lerp_amplitude = mix(max_amplitude, params.max_distance / scale, amplitude); + max_amplitude = mix(max_amplitude, lerp_amplitude, remainder); + float lerp_distance = mix(dist, min(dist, octave_distance / scale), amplitude); + dist = mix(dist, min(dist, lerp_distance), remainder); + } + } + } + + if (params.normalize) { + dist /= max_amplitude; + } + + return dist; +} + +VoronoiOutput fractal_voronoi_3d(VoronoiParams params, vec3 coord) +{ + float amplitude = 1.0; + float max_amplitude = 0.0; + float scale = 1.0; + + VoronoiOutput Output; + Output.Distance = 0.0; + Output.Color = vec3(0.0, 0.0, 0.0); + Output.Position = vec4(0.0, 0.0, 0.0, 0.0); + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + VoronoiOutput octave; + if (params.feature == SHD_VORONOI_F2) { + octave = voronoi_f2(params, coord * scale); + } + else if (params.feature == SHD_VORONOI_SMOOTH_F1 && params.smoothness != 0.0) { + octave = voronoi_smooth_f1(params, coord * scale); + } + else { + octave = voronoi_f1(params, coord * scale); + } + + if (zero_input) { + max_amplitude = 1.0; + Output = octave; + break; + } + else if (float(i) <= params.detail) { + max_amplitude += amplitude; + Output.Distance += octave.Distance * amplitude; + Output.Color += octave.Color * amplitude; + Output.Position = mix(Output.Position, octave.Position / scale, amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + max_amplitude = mix(max_amplitude, max_amplitude + amplitude, remainder); + Output.Distance = mix( + Output.Distance, Output.Distance + octave.Distance * amplitude, remainder); + Output.Color = mix(Output.Color, Output.Color + octave.Color * amplitude, remainder); + Output.Position = mix( + Output.Position, mix(Output.Position, octave.Position / scale, amplitude), remainder); + } + } + } + + if (params.normalize) { + Output.Distance /= max_amplitude * params.max_distance; + Output.Color /= max_amplitude; + } + + Output.Position = safe_divide(Output.Position, params.scale); + + return Output; +} + +VoronoiOutput fractal_voronoi_4d(VoronoiParams params, vec4 coord) +{ + float amplitude = 1.0; + float max_amplitude = 0.0; + float scale = 1.0; + + VoronoiOutput Output; + Output.Distance = 0.0; + Output.Color = vec3(0.0, 0.0, 0.0); + Output.Position = vec4(0.0, 0.0, 0.0, 0.0); + bool zero_input = params.detail == 0.0 || params.roughness == 0.0; + + for (int i = 0; i <= int(ceil(params.detail)); ++i) { + VoronoiOutput octave; + if (params.feature == SHD_VORONOI_F2) { + octave = voronoi_f2(params, coord * scale); + } + else if (params.feature == SHD_VORONOI_SMOOTH_F1 && params.smoothness != 0.0) { + octave = voronoi_smooth_f1(params, coord * scale); + } + else { + octave = voronoi_f1(params, coord * scale); + } + + if (zero_input) { + max_amplitude = 1.0; + Output = octave; + break; + } + else if (float(i) <= params.detail) { + max_amplitude += amplitude; + Output.Distance += octave.Distance * amplitude; + Output.Color += octave.Color * amplitude; + Output.Position = mix(Output.Position, octave.Position / scale, amplitude); + scale *= params.lacunarity; + amplitude *= params.roughness; + } + else { + float remainder = params.detail - floor(params.detail); + if (remainder != 0.0) { + max_amplitude = mix(max_amplitude, max_amplitude + amplitude, remainder); + Output.Distance = mix( + Output.Distance, Output.Distance + octave.Distance * amplitude, remainder); + Output.Color = mix(Output.Color, Output.Color + octave.Color * amplitude, remainder); + Output.Position = mix( + Output.Position, mix(Output.Position, octave.Position / scale, amplitude), remainder); + } + } + } + + if (params.normalize) { + Output.Distance /= max_amplitude * params.max_distance; + Output.Color /= max_amplitude; + } + + Output.Position = safe_divide(Output.Position, params.scale); + + return Output; +} + +vec3 tex_voronoi_3d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) +{ + VoronoiParams params; + params.feature = feature; + params.metric = metric; + params.scale = scale; + params.detail = clamp(detail, 0.0, 15.0); + params.roughness = clamp(roughness, 0.0, 1.0); + params.lacunarity = lacunarity; + params.smoothness = clamp(smoothness / 2.0, 0.0, 0.5); + params.exponent = exp; + params.randomness = clamp(randomness, 0.0, 1.0); + + if (feature == SHD_VORONOI_F2) { + params.max_distance = voronoi_distance(vec3(0.0), vec3(0.5 + 0.5 * params.randomness), params) * 2.0; + } else if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + params.max_distance = 0.5 + 0.5 * params.randomness; + } else { + params.max_distance = voronoi_distance(vec3(0.0), vec3(0.5 + 0.5 * params.randomness), params); + } + + params.normalize = normalize == 1; + + vec3 scaledCoord = coord * scale; + + if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + float dist = fractal_voronoi_distance_to_edge(params, scaledCoord); + if (outp == 0) return vec3(dist); + return vec3(0.0); + } + else if (feature == SHD_VORONOI_N_SPHERE_RADIUS) { + float radius = voronoi_n_sphere_radius(params, scaledCoord); + if (outp == 0) return vec3(radius); + return vec3(0.0); + } + else { + VoronoiOutput Output = fractal_voronoi_3d(params, scaledCoord); + if (outp == 0) return vec3(Output.Distance); + else if (outp == 1) return Output.Color; + return Output.Position.xyz; + } +} + +vec3 tex_voronoi_4d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) +{ + VoronoiParams params; + params.feature = feature; + params.metric = metric; + params.scale = scale; + params.detail = clamp(detail, 0.0, 15.0); + params.roughness = clamp(roughness, 0.0, 1.0); + params.lacunarity = lacunarity; + params.smoothness = clamp(smoothness / 2.0, 0.0, 0.5); + params.exponent = exp; + params.randomness = clamp(randomness, 0.0, 1.0); + + if (feature == SHD_VORONOI_F2) { + params.max_distance = voronoi_distance(vec4(0.0), vec4(0.5 + 0.5 * params.randomness), params) * 2.0; + } else if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + params.max_distance = 0.5 + 0.5 * params.randomness; + } else { + params.max_distance = voronoi_distance(vec4(0.0), vec4(0.5 + 0.5 * params.randomness), params); + } + + params.normalize = normalize == 1; + + vec4 scaledCoord = vec4(coord * scale, w * scale); + + if (feature == SHD_VORONOI_DISTANCE_TO_EDGE) { + float dist = fractal_voronoi_distance_to_edge(params, scaledCoord); + if (outp == 0) return vec3(dist); + return vec3(0.0); + } + else if (feature == SHD_VORONOI_N_SPHERE_RADIUS) { + float radius = voronoi_n_sphere_radius(params, scaledCoord); + if (outp == 0) return vec3(radius); + return vec3(0.0); + } + else { + VoronoiOutput Output = fractal_voronoi_4d(params, scaledCoord); + if (outp == 0) return vec3(Output.Distance); + else if (outp == 1) return Output.Color; + else if (outp == 2) return Output.Position.xyz; + return vec3(Output.Position.w); + } +} + +vec3 tex_voronoi_1d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) { + return tex_voronoi_3d(vec3(w, 0.0, 0.0), randomness, metric, outp, scale, exp, 0.0, detail, roughness, lacunarity, smoothness, feature, normalize); +} + +vec3 tex_voronoi_2d(vec3 coord, float randomness, int metric, int outp, float scale, float exp, float w, float detail, float roughness, float lacunarity, float smoothness, int feature, int normalize) { + return tex_voronoi_3d(vec3(coord.x, coord.y, 0.0), randomness, metric, outp, scale, exp, 0.0, detail, roughness, lacunarity, smoothness, feature, normalize); +} +""" + + +str_tex_noise = """ +//https://github.com/blender/blender/blob/main/source/blender/gpu/shaders/material/gpu_shader_material_tex_noise.glsl +uint rot(uint x, int k) { + return (x << k) | (x >> (32 - k)); +} + +void mix_hash(inout uint a, inout uint b, inout uint c) { + a -= c; a ^= rot(c, 4); c += b; + b -= a; b ^= rot(a, 6); a += c; + c -= b; c ^= rot(b, 8); b += a; + a -= c; a ^= rot(c, 16); c += b; + b -= a; b ^= rot(a, 19); a += c; + c -= b; c ^= rot(b, 4); b += a; +} + +void final_hash(inout uint a, inout uint b, inout uint c) { + c ^= b; c -= rot(b, 14); + a ^= c; a -= rot(c, 11); + b ^= a; b -= rot(a, 25); + c ^= b; c -= rot(b, 16); + a ^= c; a -= rot(c, 4); + b ^= a; b -= rot(a, 14); + c ^= b; c -= rot(b, 24); +} + +uint hash_uint(uint kx) { + uint a = 0xdeadbeefu + 17u + kx; + uint b = 0xdeadbeefu + 17u; + uint c = 0xdeadbeefu + 17u; + final_hash(a, b, c); + return c; +} + +uint hash_uint2(uint kx, uint ky) { + uint a = 0xdeadbeefu + 21u + kx; + uint b = 0xdeadbeefu + 21u + ky; + uint c = 0xdeadbeefu + 21u; + final_hash(a, b, c); + return c; +} + +uint hash_uint3(uint kx, uint ky, uint kz) { + uint a = 0xdeadbeefu + 25u + kx; + uint b = 0xdeadbeefu + 25u + ky; + uint c = 0xdeadbeefu + 25u + kz; + final_hash(a, b, c); + return c; +} + +uint hash_uint4(uint kx, uint ky, uint kz, uint kw) { + uint a = 0xdeadbeefu + 29u + kx; + uint b = 0xdeadbeefu + 29u + ky; + uint c = 0xdeadbeefu + 29u + kz; + mix_hash(a, b, c); + a += kw; + final_hash(a, b, c); + return c; +} + +float hash_float_to_float(float k) { + return float(hash_uint(floatBitsToUint(k))) / 4294967295.0; +} + +float hash_vec2_to_float(vec2 k) { + return float(hash_uint2(floatBitsToUint(k.x), floatBitsToUint(k.y))) / 4294967295.0; +} + +float hash_vec3_to_float(vec3 k) { + return float(hash_uint3(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z))) / 4294967295.0; +} + +float hash_vec4_to_float(vec4 k) { + return float(hash_uint4(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z), floatBitsToUint(k.w))) / 4294967295.0; +} + +float fade(float t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +float negate_if(float val, uint cond) { + return (cond != 0u) ? -val : val; +} + +float noise_grad(uint hash, float x) { + uint h = hash & 15u; + float g = 1.0 + float(h & 7u); + return negate_if(g, h & 8u) * x; +} + +float noise_grad(uint hash, float x, float y) { + uint h = hash & 7u; + float u = h < 4u ? x : y; + float v = 2.0 * (h < 4u ? y : x); + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +float noise_grad(uint hash, float x, float y, float z) { + uint h = hash & 15u; + float u = h < 8u ? x : y; + float vt = ((h == 12u) || (h == 14u)) ? x : z; + float v = h < 4u ? y : vt; + return negate_if(u, h & 1u) + negate_if(v, h & 2u); +} + +float noise_grad(uint hash, float x, float y, float z, float w) { + uint h = hash & 31u; + float u = h < 24u ? x : y; + float v = h < 16u ? y : z; + float s = h < 8u ? z : w; + return negate_if(u, h & 1u) + negate_if(v, h & 2u) + negate_if(s, h & 4u); +} + +float noise_perlin(float x) { + float x_floor = floor(x); + int X = int(x_floor); + float fx = x - x_floor; + float u = fade(fx); + return mix(noise_grad(hash_uint(uint(X)), fx), noise_grad(hash_uint(uint(X + 1)), fx - 1.0), u); +} + +float noise_perlin(vec2 vec) { + vec2 vec_floor = floor(vec); + ivec2 I = ivec2(vec_floor); + vec2 f = vec - vec_floor; + vec2 u = vec2(fade(f.x), fade(f.y)); + float v00 = noise_grad(hash_uint2(uint(I.x), uint(I.y)), f.x, f.y); + float v10 = noise_grad(hash_uint2(uint(I.x + 1), uint(I.y)), f.x - 1.0, f.y); + float v01 = noise_grad(hash_uint2(uint(I.x), uint(I.y + 1)), f.x, f.y - 1.0); + float v11 = noise_grad(hash_uint2(uint(I.x + 1), uint(I.y + 1)), f.x - 1.0, f.y - 1.0); + return mix(mix(v00, v10, u.x), mix(v01, v11, u.x), u.y); +} + +float noise_perlin(vec3 vec) { + vec3 vec_floor = floor(vec); + ivec3 I = ivec3(vec_floor); + vec3 f = vec - vec_floor; + vec3 u = vec3(fade(f.x), fade(f.y), fade(f.z)); + float v000 = noise_grad(hash_uint3(uint(I.x), uint(I.y), uint(I.z)), f.x, f.y, f.z); + float v100 = noise_grad(hash_uint3(uint(I.x + 1), uint(I.y), uint(I.z)), f.x - 1.0, f.y, f.z); + float v010 = noise_grad(hash_uint3(uint(I.x), uint(I.y + 1), uint(I.z)), f.x, f.y - 1.0, f.z); + float v110 = noise_grad(hash_uint3(uint(I.x + 1), uint(I.y + 1), uint(I.z)), f.x - 1.0, f.y - 1.0, f.z); + float v001 = noise_grad(hash_uint3(uint(I.x), uint(I.y), uint(I.z + 1)), f.x, f.y, f.z - 1.0); + float v101 = noise_grad(hash_uint3(uint(I.x + 1), uint(I.y), uint(I.z + 1)), f.x - 1.0, f.y, f.z - 1.0); + float v011 = noise_grad(hash_uint3(uint(I.x), uint(I.y + 1), uint(I.z + 1)), f.x, f.y - 1.0, f.z - 1.0); + float v111 = noise_grad(hash_uint3(uint(I.x + 1), uint(I.y + 1), uint(I.z + 1)), f.x - 1.0, f.y - 1.0, f.z - 1.0); + return mix(mix(mix(v000, v100, u.x), mix(v010, v110, u.x), u.y), mix(mix(v001, v101, u.x), mix(v011, v111, u.x), u.y), u.z); +} + +float noise_perlin(vec4 vec) { + vec4 vec_floor = floor(vec); + ivec4 I = ivec4(vec_floor); + vec4 f = vec - vec_floor; + vec4 u = vec4(fade(f.x), fade(f.y), fade(f.z), fade(f.w)); + float v0 = mix(mix(mix(noise_grad(hash_uint4(uint(I.x), uint(I.y), uint(I.z), uint(I.w)), f.x, f.y, f.z, f.w), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y), uint(I.z), uint(I.w)), f.x - 1.0, f.y, f.z, f.w), u.x), mix(noise_grad(hash_uint4(uint(I.x), uint(I.y + 1), uint(I.z), uint(I.w)), f.x, f.y - 1.0, f.z, f.w), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y + 1), uint(I.z), uint(I.w)), f.x - 1.0, f.y - 1.0, f.z, f.w), u.x), u.y), mix(mix(noise_grad(hash_uint4(uint(I.x), uint(I.y), uint(I.z + 1), uint(I.w)), f.x, f.y, f.z - 1.0, f.w), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y), uint(I.z + 1), uint(I.w)), f.x - 1.0, f.y, f.z - 1.0, f.w), u.x), mix(noise_grad(hash_uint4(uint(I.x), uint(I.y + 1), uint(I.z + 1), uint(I.w)), f.x, f.y - 1.0, f.z - 1.0, f.w), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y + 1), uint(I.z + 1), uint(I.w)), f.x - 1.0, f.y - 1.0, f.z - 1.0, f.w), u.x), u.y), u.z); + float v1 = mix(mix(mix(noise_grad(hash_uint4(uint(I.x), uint(I.y), uint(I.z), uint(I.w + 1)), f.x, f.y, f.z, f.w - 1.0), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y), uint(I.z), uint(I.w + 1)), f.x - 1.0, f.y, f.z, f.w - 1.0), u.x), mix(noise_grad(hash_uint4(uint(I.x), uint(I.y + 1), uint(I.z), uint(I.w + 1)), f.x, f.y - 1.0, f.z, f.w - 1.0), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y + 1), uint(I.z), uint(I.w + 1)), f.x - 1.0, f.y - 1.0, f.z, f.w - 1.0), u.x), u.y), mix(mix(noise_grad(hash_uint4(uint(I.x), uint(I.y), uint(I.z + 1), uint(I.w + 1)), f.x, f.y, f.z - 1.0, f.w - 1.0), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y), uint(I.z + 1), uint(I.w + 1)), f.x - 1.0, f.y, f.z - 1.0, f.w - 1.0), u.x), mix(noise_grad(hash_uint4(uint(I.x), uint(I.y + 1), uint(I.z + 1), uint(I.w + 1)), f.x, f.y - 1.0, f.z - 1.0, f.w - 1.0), noise_grad(hash_uint4(uint(I.x + 1), uint(I.y + 1), uint(I.z + 1), uint(I.w + 1)), f.x - 1.0, f.y - 1.0, f.z - 1.0, f.w - 1.0), u.x), u.y), u.z); + return mix(v0, v1, u.w); +} + +float compatible_mod(float a, float b) { + return (b != 0.0) ? (a - float(int(a / b)) * b) : 0.0; +} +vec2 compatible_mod(vec2 a, float b) { return vec2(compatible_mod(a.x, b), compatible_mod(a.y, b)); } +vec3 compatible_mod(vec3 a, float b) { return vec3(compatible_mod(a.x, b), compatible_mod(a.y, b), compatible_mod(a.z, b)); } +vec4 compatible_mod(vec4 a, float b) { return vec4(compatible_mod(a.x, b), compatible_mod(a.y, b), compatible_mod(a.z, b), compatible_mod(a.w, b)); } + +float snoise(float p) { + float precision_correction = 0.5 * float(abs(p) >= 1000000.0); + p = compatible_mod(p, 100000.0) + precision_correction; + return 0.25 * noise_perlin(p); +} +float snoise(vec2 p) { + vec2 precision_correction = 0.5 * vec2(float(abs(p.x) >= 1000000.0), float(abs(p.y) >= 1000000.0)); + p = compatible_mod(p, 100000.0) + precision_correction; + return 0.6616 * noise_perlin(p); +} +float snoise(vec3 p) { + vec3 precision_correction = 0.5 * vec3(float(abs(p.x) >= 1000000.0), float(abs(p.y) >= 1000000.0), float(abs(p.z) >= 1000000.0)); + p = compatible_mod(p, 100000.0) + precision_correction; + return 0.9820 * noise_perlin(p); +} +float snoise(vec4 p) { + vec4 precision_correction = 0.5 * vec4(float(abs(p.x) >= 1000000.0), float(abs(p.y) >= 1000000.0), float(abs(p.z) >= 1000000.0), float(abs(p.w) >= 1000000.0)); + p = compatible_mod(p, 100000.0) + precision_correction; + return 0.8344 * noise_perlin(p); +} + +#define DEFINE_NOISE_FRACTAL(T) \\\ +float noise_fbm(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ + T p = co; \\\ + float fscale = 1.0; \\\ + float amp = 1.0; \\\ + float maxamp = 0.0; \\\ + float sum = 0.0; \\\ + for (int i = 0; i <= int(detail); i++) { \\\ + float t = snoise(fscale * p); \\\ + sum += t * amp; \\\ + maxamp += amp; \\\ + amp *= roughness; \\\ + fscale *= lacunarity; \\\ + } \\\ + float rmd = detail - floor(detail); \\\ + if (rmd != 0.0) { \\\ + float t = snoise(fscale * p); \\\ + float sum2 = sum + t * amp; \\\ + return normalize ? mix(0.5 * sum / maxamp + 0.5, 0.5 * sum2 / (maxamp + amp) + 0.5, rmd) : mix(sum, sum2, rmd); \\\ + } else { \\\ + return normalize ? 0.5 * sum / maxamp + 0.5 : sum; \\\ + } \\\ +} \\\ +float noise_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ + T p = co; \\\ + float value = 1.0; \\\ + float pwr = 1.0; \\\ + for (int i = 0; i <= int(detail); i++) { \\\ + value *= (pwr * snoise(p) + 1.0); \\\ + pwr *= roughness; \\\ + p *= lacunarity; \\\ + } \\\ + float rmd = detail - floor(detail); \\\ + if (rmd != 0.0) { \\\ + value *= (rmd * pwr * snoise(p) + 1.0); \\\ + } \\\ + return value; \\\ +} \\\ +float noise_hetero_terrain(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ + T p = co; \\\ + float pwr = roughness; \\\ + float value = offset + snoise(p); \\\ + p *= lacunarity; \\\ + for (int i = 1; i <= int(detail); i++) { \\\ + float increment = (snoise(p) + offset) * pwr * value; \\\ + value += increment; \\\ + pwr *= roughness; \\\ + p *= lacunarity; \\\ + } \\\ + float rmd = detail - floor(detail); \\\ + if (rmd != 0.0) { \\\ + float increment = (snoise(p) + offset) * pwr * value; \\\ + value += rmd * increment; \\\ + } \\\ + return value; \\\ +} \\\ +float noise_hybrid_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ + T p = co; \\\ + float pwr = 1.0; \\\ + float value = 0.0; \\\ + float weight = 1.0; \\\ + for (int i = 0; (weight > 0.001) && (i <= int(detail)); i++) { \\\ + if (weight > 1.0) weight = 1.0; \\\ + float signal = (snoise(p) + offset) * pwr; \\\ + pwr *= roughness; \\\ + value += weight * signal; \\\ + weight *= gain * signal; \\\ + p *= lacunarity; \\\ + } \\\ + float rmd = detail - floor(detail); \\\ + if ((rmd != 0.0) && (weight > 0.001)) { \\\ + if (weight > 1.0) weight = 1.0; \\\ + float signal = (snoise(p) + offset) * pwr; \\\ + value += rmd * weight * signal; \\\ + } \\\ + return value; \\\ +} \\\ +float noise_ridged_multi_fractal(T co, float detail, float roughness, float lacunarity, float offset, float gain, bool normalize) { \\\ + T p = co; \\\ + float pwr = roughness; \\\ + float signal = offset - abs(snoise(p)); \\\ + signal *= signal; \\\ + float value = signal; \\\ + float weight = 1.0; \\\ + for (int i = 1; i <= int(detail); i++) { \\\ + p *= lacunarity; \\\ + weight = clamp(signal * gain, 0.0, 1.0); \\\ + signal = offset - abs(snoise(p)); \\\ + signal *= signal; \\\ + signal *= weight; \\\ + value += signal * pwr; \\\ + pwr *= roughness; \\\ + } \\\ + return value; \\\ +} + +DEFINE_NOISE_FRACTAL(float) +DEFINE_NOISE_FRACTAL(vec2) +DEFINE_NOISE_FRACTAL(vec3) +DEFINE_NOISE_FRACTAL(vec4) + +float random_float_offset(float seed) { return 100.0 + hash_float_to_float(seed) * 100.0; } +vec2 random_vec2_offset(float seed) { return vec2(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0); } +vec3 random_vec3_offset(float seed) { return vec3(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 2.0)) * 100.0); } +vec4 random_vec4_offset(float seed) { return vec4(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 2.0)) * 100.0, 100.0 + hash_vec2_to_float(vec2(seed, 3.0)) * 100.0); } + +vec3 hash_float_to_vec3(float k) { + return vec3(hash_float_to_float(k), hash_vec2_to_float(vec2(k, 1.0)), hash_vec2_to_float(vec2(k, 2.0))); +} + +vec3 hash_vec2_to_vec3(vec2 k) { + return vec3(hash_vec2_to_float(k), hash_vec3_to_float(vec3(k, 1.0)), hash_vec3_to_float(vec3(k, 2.0))); +} + +vec3 hash_vec3_to_vec3(vec3 k) { + return vec3(hash_vec3_to_float(k), hash_vec4_to_float(vec4(k, 1.0)), hash_vec4_to_float(vec4(k, 2.0))); +} + +vec3 hash_vec4_to_vec3(vec4 k) { + return vec3(hash_vec4_to_float(k.xyzw), hash_vec4_to_float(k.zxwy), hash_vec4_to_float(k.wzyx)); } """ @@ -269,15 +2202,71 @@ vec3 wavelength_to_rgb(const float t) { """ str_tex_magic = """ -vec3 tex_magic(const vec3 p) { - float a = 1.0 - (sin(p.x) + sin(p.y)); - float b = 1.0 - sin(p.x - p.y); - float c = 1.0 - sin(p.x + p.y); - return vec3(a, b, c); +//https://github.com/blender/blender/blob/main/source/blender/gpu/shaders/material/gpu_shader_material_tex_magic.glsl +vec3 tex_magic(vec3 p, float distortion, int depth) { + p = mod(p, 6.2831853f); + + float x = sin((p.x + p.y + p.z) * 5.0f); + float y = cos((-p.x + p.y - p.z) * 5.0f); + float z = -cos((-p.x - p.y + p.z) * 5.0f); + + if (depth > 0) { + x *= distortion; + y *= distortion; + z *= distortion; + y = -cos(x - y + z); + y *= distortion; + if (depth > 1) { + x = cos(x - y - z); + x *= distortion; + if (depth > 2) { + z = sin(-x - y - z); + z *= distortion; + if (depth > 3) { + x = -cos(-x + y - z); + x *= distortion; + if (depth > 4) { + y = -sin(-x + y + z); + y *= distortion; + if (depth > 5) { + y = -cos(-x + y + z); + y *= distortion; + if (depth > 6) { + x = cos(x + y + z); + x *= distortion; + if (depth > 7) { + z = sin(x + y - z); + z *= distortion; + if (depth > 8) { + x = -cos(-x - y + z); + x *= distortion; + if (depth > 9) { + y = -sin(x - y + z); + y *= distortion; + } + } + } + } + } + } + } + } + } + } + + if (distortion != 0.0f) { + distortion *= 2.0f; + x /= distortion; + y /= distortion; + z /= distortion; + } + + return vec3(0.5f - x, 0.5f - y, 0.5f - z); } -float tex_magic_f(const vec3 p) { - vec3 c = tex_magic(p); - return (c.x + c.y + c.z) / 3.0; + +float tex_magic_f(vec3 p, float distortion, int depth) { + vec3 c = tex_magic(p, distortion, depth); + return (c.x + c.y + c.z) / 3.0f; } """ @@ -418,17 +2407,232 @@ float tex_brick_blender_f(vec3 co, str_tex_wave = """ -float tex_wave_f(const vec3 p, const int type, const int profile, const float dist, const float detail, const float detail_scale, const float phase_offset) { +float tex_wave_f(const vec3 p, const int type, const int d, const int profile, const float dist, const float detail, const float detail_scale, const float phase_offset, const float detail_roughness) { + vec3 cp = (p + 0.000001) * 0.999999; float n; - if(type == 0) n = (p.x + p.y + p.z) * 9.5; - else n = length(p) * 13.0; - if(dist != 0.0) n += dist * fractal_noise(p * detail_scale, detail) * 2.0 - 1.0; - n += phase_offset; - if(profile == 0) { return 0.5 + 0.5 * sin(n - PI); } + + if (type == 0) { + if (d == 0) n = cp.x * 20.0; + else if (d == 1) n = cp.y * 20.0; + else if (d == 2) n = cp.z * 20.0; + else n = (cp.x + cp.y + cp.z) * 10.0; + } else { - n /= 2.0 * PI; + vec3 rp = cp; + if (d == 0) rp *= vec3(0.0, 1.0, 1.0); + else if (d == 1) rp *= vec3(1.0, 0.0, 1.0); + else if (d == 2) rp *= vec3(1.0, 1.0, 0.0); + n = length(rp) * 20.0; + } + + n += phase_offset; + + if (dist != 0.0) { + n += dist * (noise_fbm(cp * detail_scale, detail, detail_roughness, 2.0, 0.0, 0.0, true) * 2.0 - 1.0); + } + + if (profile == 0) { + return 0.5 + 0.5 * sin(n - 1.57079632679); + } + else if (profile == 1) { + n /= 6.28318530718; return n - floor(n); } + else { + n /= 6.28318530718; + return abs(2.0 * (n - floor(n + 0.5))); + } +} +""" + +str_tex_gabor = """ +uint hash_uint3(uint kx, uint ky, uint kz) { + uint a, b, c; + a = b = c = 0xdeadbeefu + (3u << 2u) + 13u; + c += kz; + b += ky; + a += kx; + c ^= b; c -= (b << 14u) | (b >> 18u); + a ^= c; a -= (c << 11u) | (c >> 21u); + b ^= a; b -= (a << 25u) | (a >> 7u); + c ^= b; c -= (b << 16u) | (b >> 16u); + a ^= c; a -= (c << 4u) | (c >> 28u); + b ^= a; b -= (a << 14u) | (a >> 18u); + c ^= b; c -= (b << 24u) | (b >> 8u); + return c; +} + +uint hash_uint4(uint kx, uint ky, uint kz, uint kw) { + uint a, b, c; + a = b = c = 0xdeadbeefu + (4u << 2u) + 13u; + a += kx; b += ky; c += kz; + a -= c; a ^= (c << 4u) | (c >> 28u); c += b; + b -= a; b ^= (a << 6u) | (a >> 26u); a += c; + c -= b; c ^= (b << 8u) | (b >> 24u); b += a; + a -= c; a ^= (c << 16u) | (c >> 16u); c += b; + b -= a; b ^= (a << 19u) | (a >> 13u); a += c; + c -= b; c ^= (b << 4u) | (b >> 28u); b += a; + a += kw; + c ^= b; c -= (b << 14u) | (b >> 18u); + a ^= c; a -= (c << 11u) | (c >> 21u); + b ^= a; b -= (a << 25u) | (a >> 7u); + c ^= b; c -= (b << 16u) | (b >> 16u); + a ^= c; a -= (c << 4u) | (c >> 28u); + b ^= a; b -= (a << 14u) | (a >> 18u); + c ^= b; c -= (b << 24u) | (b >> 8u); + return c; +} + +float hash_vec3_to_float(vec3 k) { + return float(hash_uint3(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z))) / float(0xFFFFFFFFu); +} + +float hash_vec4_to_float(vec4 k) { + return float(hash_uint4(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z), floatBitsToUint(k.w))) / float(0xFFFFFFFFu); +} + +vec2 hash_vec3_to_vec2(vec3 k) { + return vec2(hash_vec3_to_float(k.xyz), hash_vec3_to_float(k.zxy)); +} + +vec2 hash_vec4_to_vec2(vec4 k) { + return vec2(hash_vec4_to_float(k.xyzw), hash_vec4_to_float(k.zxwy)); +} + +vec3 hash_vec4_to_vec3(vec4 k) { + return vec3(hash_vec4_to_float(k.xyzw), hash_vec4_to_float(k.zxwy), hash_vec4_to_float(k.wzyx)); +} + +vec2 compute_2d_gabor_kernel(vec2 position, float frequency, float orientation) { + float distance_squared = dot(position, position); + float hann_window = 0.5 + 0.5 * cos(3.14159265359 * distance_squared); + float gaussian_envelop = exp(-3.14159265359 * distance_squared); + vec2 frequency_vector = frequency * vec2(cos(orientation), sin(orientation)); + float angle = 6.28318530718 * dot(position, frequency_vector); + return gaussian_envelop * hann_window * vec2(cos(angle), sin(angle)); +} + +float compute_2d_gabor_standard_deviation() { + return sqrt(8.0 * 0.5 * 0.25); +} + +vec2 compute_2d_gabor_noise_cell(vec2 cell, vec2 position, float frequency, float isotropy, float base_orientation) { + vec2 noise = vec2(0.0); + for (int i = 0; i < 8; ++i) { + vec3 seed_for_orientation = vec3(cell, float(i * 3)); + vec3 seed_for_kernel_center = vec3(cell, float(i * 3 + 1)); + vec3 seed_for_weight = vec3(cell, float(i * 3 + 2)); + float random_orientation = (hash_vec3_to_float(seed_for_orientation) - 0.5) * 3.14159265359; + float orientation = base_orientation + random_orientation * isotropy; + vec2 kernel_center = hash_vec3_to_vec2(seed_for_kernel_center); + vec2 position_in_kernel_space = position - kernel_center; + if (dot(position_in_kernel_space, position_in_kernel_space) >= 1.0) continue; + float weight = hash_vec3_to_float(seed_for_weight) < 0.5 ? -1.0 : 1.0; + noise += weight * compute_2d_gabor_kernel(position_in_kernel_space, frequency, orientation); + } + return noise; +} + +vec2 compute_2d_gabor_noise(vec2 coordinates, float frequency, float isotropy, float base_orientation) { + vec2 cell_position = floor(coordinates); + vec2 local_position = coordinates - cell_position; + vec2 sum = vec2(0.0); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec2 cell_offset = vec2(float(i), float(j)); + sum += compute_2d_gabor_noise_cell(cell_position + cell_offset, local_position - cell_offset, frequency, isotropy, base_orientation); + } + } + return sum; +} + +vec2 compute_3d_gabor_kernel(vec3 position, float frequency, vec3 orientation) { + float distance_squared = dot(position, position); + float hann_window = 0.5 + 0.5 * cos(3.14159265359 * distance_squared); + float gaussian_envelop = exp(-3.14159265359 * distance_squared); + vec3 frequency_vector = frequency * orientation; + float angle = 6.28318530718 * dot(position, frequency_vector); + return gaussian_envelop * hann_window * vec2(cos(angle), sin(angle)); +} + +float compute_3d_gabor_standard_deviation() { + return sqrt(8.0 * 0.5 * (1.0 / (4.0 * 1.41421356237))); +} + +vec3 compute_3d_orientation(vec3 orientation, float isotropy, vec4 seed) { + if (isotropy == 0.0) return orientation; + float inclination = acos(clamp(orientation.z, -1.0, 1.0)); + float azimuth = sign(orientation.y) * acos(orientation.x / length(orientation.xy)); + vec2 random_angles = hash_vec4_to_vec2(seed) * 3.14159265359; + inclination += random_angles.x * isotropy; + azimuth += random_angles.y * isotropy; + return vec3(sin(inclination) * cos(azimuth), sin(inclination) * sin(azimuth), cos(inclination)); +} + +vec2 compute_3d_gabor_noise_cell(vec3 cell, vec3 position, float frequency, float isotropy, vec3 base_orientation) { + vec2 noise = vec2(0.0); + for (int i = 0; i < 8; ++i) { + vec4 seed_for_orientation = vec4(cell, float(i * 3)); + vec4 seed_for_kernel_center = vec4(cell, float(i * 3 + 1)); + vec4 seed_for_weight = vec4(cell, float(i * 3 + 2)); + vec3 orientation = compute_3d_orientation(base_orientation, isotropy, seed_for_orientation); + vec3 kernel_center = hash_vec4_to_vec3(seed_for_kernel_center); + vec3 position_in_kernel_space = position - kernel_center; + if (dot(position_in_kernel_space, position_in_kernel_space) >= 1.0) continue; + float weight = hash_vec4_to_float(seed_for_weight) < 0.5 ? -1.0 : 1.0; + noise += weight * compute_3d_gabor_kernel(position_in_kernel_space, frequency, orientation); + } + return noise; +} + +vec2 compute_3d_gabor_noise(vec3 coordinates, float frequency, float isotropy, vec3 base_orientation) { + vec3 cell_position = floor(coordinates); + vec3 local_position = coordinates - cell_position; + vec2 sum = vec2(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cell_offset = vec3(float(i), float(j), float(k)); + sum += compute_3d_gabor_noise_cell(cell_position + cell_offset, local_position - cell_offset, frequency, isotropy, base_orientation); + } + } + } + return sum; +} + +vec3 tex_gabor_core(vec3 coordinates, float scale, float frequency, float anisotropy, float orientation_2d, vec3 orientation_3d, float type) { + vec3 scaled_coordinates = coordinates * scale; + float isotropy = 1.0 - clamp(anisotropy, 0.0, 1.0); + frequency = max(0.001, frequency); + vec2 phasor = vec2(0.0); + float standard_deviation = 1.0; + if (type == 0.0) { + phasor = compute_2d_gabor_noise(scaled_coordinates.xy, frequency, isotropy, orientation_2d); + standard_deviation = compute_2d_gabor_standard_deviation(); + } + else if (type == 1.0) { + float len = length(orientation_3d); + vec3 orientation = len > 0.0 ? orientation_3d / len : vec3(0.0, 0.0, 1.0); + phasor = compute_3d_gabor_noise(scaled_coordinates, frequency, isotropy, orientation); + standard_deviation = compute_3d_gabor_standard_deviation(); + } + float normalization_factor = 6.0 * standard_deviation; + float output_value = (phasor.y / normalization_factor) * 0.5 + 0.5; + float output_phase = (atan(phasor.y, phasor.x) + 3.14159265359) / 6.28318530718; + float output_intensity = length(phasor) / normalization_factor; + return vec3(output_value, output_phase, output_intensity); +} + +float tex_gabor_value(vec3 coordinates, float scale, float frequency, float anisotropy, float orientation_2d, vec3 orientation_3d, float type) { + return tex_gabor_core(coordinates, scale, frequency, anisotropy, orientation_2d, orientation_3d, type).x; +} + +float tex_gabor_phase(vec3 coordinates, float scale, float frequency, float anisotropy, float orientation_2d, vec3 orientation_3d, float type) { + return tex_gabor_core(coordinates, scale, frequency, anisotropy, orientation_2d, orientation_3d, type).y; +} + +float tex_gabor_intensity(vec3 coordinates, float scale, float frequency, float anisotropy, float orientation_2d, vec3 orientation_3d, float type) { + return tex_gabor_core(coordinates, scale, frequency, anisotropy, orientation_2d, orientation_3d, type).z; } """ diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_color.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_color.py index f910a25e..2696f5d5 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_color.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_color.py @@ -119,37 +119,35 @@ def _parse_mixrgb(node: bpy.types.ShaderNodeMixRGB, out_socket: bpy.types.NodeSo elif blend == 'SUBTRACT': out_col = 'mix({0}, {0} - {1}, {2})'.format(col1, col2, fac) elif blend == 'SCREEN': - out_col = '(vec3(1.0) - (vec3(1.0 - {2}) + {2} * (vec3(1.0) - {1})) * (vec3(1.0) - {0}))'.format(col1, col2, fac) + out_col = 'mix({0}, vec3(1.0) - (vec3(1.0) - {0}) * (vec3(1.0) - {1}), {2})'.format(col1, col2, fac) elif blend == 'DIVIDE': - out_col = '(vec3((1.0 - {2}) * {0} + {2} * {0} / {1}))'.format(col1, col2, fac) + out_col = 'mix({0}, {0} / max({1}, vec3(0.000001)), {2})'.format(col1, col2, fac) elif blend == 'DIFFERENCE': out_col = 'mix({0}, abs({0} - {1}), {2})'.format(col1, col2, fac) elif blend == 'DARKEN': - out_col = 'min({0}, {1} * {2})'.format(col1, col2, fac) + out_col = 'mix({0}, min({0}, {1}), {2})'.format(col1, col2, fac) elif blend == 'LIGHTEN': - out_col = 'max({0}, {1} * {2})'.format(col1, col2, fac) + out_col = 'mix({0}, max({0}, {1}), {2})'.format(col1, col2, fac) elif blend == 'OVERLAY': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix + overlay = 'vec3({0}.r < 0.5 ? 2.0 * {0}.r * {1}.r : 1.0 - 2.0 * (1.0 - {0}.r) * (1.0 - {1}.r), {0}.g < 0.5 ? 2.0 * {0}.g * {1}.g : 1.0 - 2.0 * (1.0 - {0}.g) * (1.0 - {1}.g), {0}.b < 0.5 ? 2.0 * {0}.b * {1}.b : 1.0 - 2.0 * (1.0 - {0}.b) * (1.0 - {1}.b))'.format(col1, col2) + out_col = 'mix({0}, {1}, {2})'.format(col1, overlay, fac) elif blend == 'DODGE': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix + dodge = '{0} / max(vec3(1.0) - {1}, vec3(0.000001))'.format(col1, col2) + out_col = 'mix({0}, {1}, {2})'.format(col1, dodge, fac) elif blend == 'BURN': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix - elif blend == 'HUE': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix - elif blend == 'SATURATION': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix - elif blend == 'VALUE': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix - elif blend == 'COLOR': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix + burn = 'vec3(1.0) - (vec3(1.0) - {0}) / max({1}, vec3(0.000001))'.format(col1, col2) + out_col = 'mix({0}, {1}, {2})'.format(col1, burn, fac) elif blend == 'SOFT_LIGHT': - out_col = '((1.0 - {2}) * {0} + {2} * ((vec3(1.0) - {0}) * {1} * {0} + {0} * (vec3(1.0) - (vec3(1.0) - {1}) * (vec3(1.0) - {0}))))'.format(col1, col2, fac) + soft = '(vec3(1.0) - {1}) * {0} * {0} + {1} * (2.0 * {0} * (vec3(1.0) - {0}) + sqrt({0}) * (2.0 * {0} - vec3(1.0)))'.format(col1, col2) + out_col = 'mix({0}, {1}, {2})'.format(col1, soft, fac) elif blend == 'LINEAR_LIGHT': - out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) # Revert to mix - # out_col = '({0} + {2} * (2.0 * ({1} - vec3(0.5))))'.format(col1, col2, fac_var) + linear = '{0} + 2.0 * {1} - vec3(1.0)'.format(col1, col2) + out_col = 'mix({0}, {1}, {2})'.format(col1, linear, fac) + elif blend in ['HUE', 'SATURATION', 'COLOR', 'VALUE']: + out_col = 'mix({0}, {1}, {2})'.format(col1, col2, fac) else: log.warn(f'MixRGB node: unsupported blend type {node.blend_type}.') - return col1 + out_col = col1 if node.clamp_result: return 'clamp({0}, vec3(0.0), vec3(1.0))'.format(out_col) diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py index 22566fe2..8aaec0b0 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py @@ -83,62 +83,58 @@ def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocke def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: - # Alpha (TODO: make ColorRamp calculation vec4-based and split afterwards) - if out_socket == node.outputs[1]: - return '1.0' - input_fac: bpy.types.NodeSocket = node.inputs[0] - fac: str = c.parse_value_input(input_fac) if input_fac.is_linked else c.to_vec1(input_fac.default_value) interp = node.color_ramp.interpolation elems = node.color_ramp.elements + use_color_out = out_socket == node.outputs[0] + ramp_store = c.node_name(node.name) + '_res' + state.get_parser_pass_suffix() + + if c.is_parsed(ramp_store): + return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a' + if len(elems) == 1: - return c.to_vec3(elems[0].color) + res = f'vec4({elems[0].color[0]}, {elems[0].color[1]}, {elems[0].color[2]}, {elems[0].color[3]})' + state.curshader.write(f'vec4 {ramp_store} = {res};') + state.parsed.add(ramp_store) + return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a' - # Write color array - # The last entry is included twice so that the interpolation - # between indices works (no out of bounds error) cols_var = c.node_name(node.name).upper() + '_COLS' - if state.current_pass == ParserPass.REGULAR: - cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems) - cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})' - state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1) + cols_entries = ', '.join(f'vec4({e.color[0]}, {e.color[1]}, {e.color[2]}, {e.color[3]})' for e in elems) + cols_entries += f', vec4({elems[-1].color[0]}, {elems[-1].color[1]}, {elems[-1].color[2]}, {elems[-1].color[3]})' + state.curshader.add_const("vec4", cols_var, cols_entries, array_size=len(elems) + 1) fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix() state.curshader.write(f'float {fac_var} = {fac};') - # Get index of the nearest left element relative to the factor - index = '0 + ' - index += ' + '.join([f'(({fac_var} > {elems[i].position}) ? 1 : 0)' for i in range(1, len(elems))]) + index = '0' + for i in range(1, len(elems)): + index += f' + ({fac_var} > {elems[i].position} ? 1 : 0)' - # Write index index_var = c.node_name(node.name) + '_i' + state.get_parser_pass_suffix() state.curshader.write(f'int {index_var} = {index};') if interp == 'CONSTANT': - return f'{cols_var}[{index_var}]' - - # Linear interpolation + state.curshader.write(f'vec4 {ramp_store} = {cols_var}[{index_var}];') else: - # Write factor array facs_var = c.node_name(node.name).upper() + '_FACS' if state.current_pass == ParserPass.REGULAR: - facs_entries = ', '.join(str(elem.position) for elem in elems) - # Add one more entry at the rightmost position so that the - # interpolation between indices works (no out of bounds error) + facs_entries = ', '.join(str(e.position) for e in elems) facs_entries += ', 1.0' state.curshader.add_const("float", facs_var, facs_entries, array_size=len(elems) + 1) - # Mix color - prev_stop_fac = f'{facs_var}[{index_var}]' - next_stop_fac = f'{facs_var}[{index_var} + 1]' - prev_stop_col = f'{cols_var}[{index_var}]' - next_stop_col = f'{cols_var}[{index_var} + 1]' - rel_pos = f'({fac_var} - {prev_stop_fac}) * (1.0 / ({next_stop_fac} - {prev_stop_fac}))' + f_a = f'{facs_var}[{index_var}]' + f_b = f'{facs_var}[{index_var} + 1]' + c_a = f'{cols_var}[{index_var}]' + c_b = f'{cols_var}[{index_var} + 1]' - return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))' + rel_pos = f'({fac_var} - {f_a}) * (1.0 / ({f_b} - {f_a}))' + state.curshader.write(f'vec4 {ramp_store} = mix({c_a}, {c_b}, max({rel_pos}, 0.0));') + + state.parsed.add(ramp_store) + return f'{ramp_store}.rgb' if use_color_out else f'{ramp_store}.a' if bpy.app.version > (3, 2, 0): def parse_combine_color(node: bpy.types.ShaderNodeCombineColor, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: @@ -314,8 +310,9 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'trunc({0})'.format(val1) elif op == 'FRACT': out_val = 'fract({0})'.format(val1) - elif op == 'MODULO': - # out_val = 'float({0} % {1})'.format(val1, val2) + elif op == 'MODULO': #TRUNCATED MODULO + out_val = '({0} - {1} * trunc({0} / {1}))'.format(val1, val2) + elif op == 'FLOORED_MODULO': out_val = 'mod({0}, {1})'.format(val1, val2) elif op == 'WRAP': val3 = c.parse_value_input(node.inputs[2]) diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_input.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_input.py index c37c611e..c44bc03a 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_input.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_input.py @@ -37,7 +37,7 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N return c.cast_value('time', from_type='float', to_type=out_type) # UV maps (higher priority) and vertex colors - if node.attribute_type == 'GEOMETRY': + if node.attribute_type == 'GEOMETRY' and state.context == ParserContext.OBJECT: # Alpha output. Leenkx doesn't support vertex colors with alpha # values yet and UV maps don't have an alpha channel @@ -159,7 +159,7 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types. # True Normal elif out_socket == node.outputs[3]: state.dxdy_varying_input_value = True - return 'n' if state.curshader.shader_type == 'frag' else 'wnormal' + return 'normalize(cross(dFdy(wposition), dFdx(wposition)))' if state.curshader.shader_type == 'frag' else 'wnormal' # Incoming elif out_socket == node.outputs[4]: state.dxdy_varying_input_value = True @@ -173,10 +173,18 @@ def parse_geometry(node: bpy.types.ShaderNodeNewGeometry, out_socket: bpy.types. return '(1.0 - float(gl_FrontFacing))' if state.context == ParserContext.OBJECT else '0.0' # Pointiness elif out_socket == node.outputs[7]: - return '0.0' + state.dxdy_varying_input_value = True + if state.curshader.shader_type == 'frag': + return 'length(cross(n, normalize(cross(dFdy(wposition), dFdx(wposition)))))' + else: + return '0.0' # Random Per Island elif out_socket == node.outputs[8]: - return '0.0' + state.dxdy_varying_input_value = True + if state.curshader.shader_type == 'frag': + return '((sin(mposition.x * 5.12 + sin(mposition.y * 8.63)) * sin(mposition.y * 6.41 + sin(mposition.z * 9.15)) * sin(mposition.z * 7.34 + sin(mposition.x * 4.82))) * 0.5 + 0.5)' + else: + return '0.0' def parse_hairinfo(node: bpy.types.ShaderNodeHairInfo, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: @@ -196,7 +204,8 @@ def parse_objectinfo(node: bpy.types.ShaderNodeObjectInfo, out_socket: bpy.types if out_socket == node.outputs[0]: if state.context == ParserContext.WORLD: return c.to_vec3((0.0, 0.0, 0.0)) - return 'wposition' + state.curshader.add_uniform('mat4 W', link='_worldMatrix') + return 'vec3(W[3][0], W[3][1], W[3][2])' # Color elif out_socket == node.outputs[1]: @@ -207,15 +216,35 @@ def parse_objectinfo(node: bpy.types.ShaderNodeObjectInfo, out_socket: bpy.types return c.to_vec3((0.0, 0.0, 0.0)) return c.to_vec3([background_node.inputs[1].default_value] * 3) - # TODO: Implement object color in Iron - # state.curshader.add_uniform('vec3 objectInfoColor', link='_objectInfoColor') - # return 'objectInfoColor' - return c.to_vec3((1.0, 1.0, 1.0)) + if mat_state.uses_instancing: + state.vert.add_out(f'vec3 irandc') + state.frag.add_in(f'vec3 irandc') + state.vert.write("irandc = vec3(" \ + "fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 12.9898 + 78.233) * 43758.5453), " \ + "fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 39.3456 + 12.123) * 43758.5453), " \ + "fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 78.2330 + 45.678) * 43758.5453)" \ + ");" + ) + return 'irandc' + + state.curshader.add_uniform('float objRandom', link='_objectInfoRandom') + return "vec3(" \ + "fract(sin(objRandom * 12.9898 + 78.233) * 43758.5453), " \ + "fract(sin(objRandom * 39.3456 + 12.123) * 43758.5453), " \ + "fract(sin(objRandom * 78.2330 + 45.678) * 43758.5453)" \ + ")" + #return c.to_vec3((1.0, 1.0, 1.0)) # Alpha elif out_socket == node.outputs[2]: - # TODO, see color output above - return '0.0' + if mat_state.uses_instancing: + state.vert.add_out(f'float iranda') + state.frag.add_in(f'float iranda') + state.vert.write("iranda = fract(sin(fract(sin(gl_InstanceID) * 43758.5453) * 12.9898) * 43758.5453);") + return 'iranda' + + state.curshader.add_uniform('float objAlphaRandom', link='_objectInfoRandom') + return 'fract(sin(objAlphaRandom * 12.9898) * 43758.5453)' # Object Index elif out_socket == node.outputs[3]: @@ -316,7 +345,8 @@ def parse_texcoord(node: bpy.types.ShaderNodeTexCoord, out_socket: bpy.types.Nod state.dxdy_varying_input_value = True return 'mposition' elif out_socket == node.outputs[4]: # Camera - return 'vec3(0.0)' # 'vposition' + state.curshader.add_uniform('mat4 V', link='_viewMatrix') + return '(V * vec4(wposition, 1.0)).xyz' elif out_socket == node.outputs[5]: # Window # TODO: Don't use gl_FragCoord here, it uses different axes on different graphics APIs state.frag.add_uniform('vec2 screenSize', link='_screenSize') @@ -326,7 +356,8 @@ def parse_texcoord(node: bpy.types.ShaderNodeTexCoord, out_socket: bpy.types.Nod if state.context == ParserContext.WORLD: state.dxdy_varying_input_value = True return 'n' - return 'vec3(0.0)' + state.curshader.add_uniform('vec3 cameraPos', link='_cameraPosition') + return 'reflect(normalize(wposition - cameraPos), n)' def parse_uvmap(node: bpy.types.ShaderNodeUVMap, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: @@ -352,10 +383,11 @@ def parse_uvmap(node: bpy.types.ShaderNodeUVMap, out_socket: bpy.types.NodeSocke def parse_fresnel(node: bpy.types.ShaderNodeFresnel, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: state.curshader.add_function(c_functions.str_fresnel) ior = c.parse_value_input(node.inputs[0]) + if node.inputs[1].is_linked: - dotnv = 'dot({0}, vVec)'.format(c.parse_vector_input(node.inputs[1])) + dotnv = 'clamp(dot({0}, vVec), 0.0, 1.0)'.format(c.parse_vector_input(node.inputs[1])) else: - dotnv = 'dotNV' + dotnv = 'clamp(dotNV, 0.0, 1.0)' state.dxdy_varying_input_value = True return 'fresnel({0}, {1})'.format(ior, dotnv) @@ -363,20 +395,24 @@ def parse_fresnel(node: bpy.types.ShaderNodeFresnel, out_socket: bpy.types.NodeS def parse_layerweight(node: bpy.types.ShaderNodeLayerWeight, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: blend = c.parse_value_input(node.inputs[0]) + if node.inputs[1].is_linked: - dotnv = 'dot({0}, vVec)'.format(c.parse_vector_input(node.inputs[1])) + dotnv = 'clamp(dot({0}, vVec), 0.0, 1.0)'.format(c.parse_vector_input(node.inputs[1])) else: - dotnv = 'dotNV' + dotnv = 'clamp(dotNV, 0.0, 1.0)' state.dxdy_varying_input_value = True # Fresnel if out_socket == node.outputs[0]: state.curshader.add_function(c_functions.str_fresnel) - return 'fresnel(1.0 / (1.0 - {0}), {1})'.format(blend, dotnv) + return 'fresnel((1.0 + sqrt(clamp({0}, 0.0, 0.99))) / max(1.0 - sqrt(clamp({0}, 0.0, 0.99)), 1e-5), {1})'.format(blend, dotnv) + # Facing elif out_socket == node.outputs[1]: - return '(1.0 - pow({0}, ({1} < 0.5) ? 2.0 * {1} : 0.5 / (1.0 - {1})))'.format(dotnv, blend) + return '({0} < 0.5) ? 1.0 - pow({1}, 2.0 * {0}) : 1.0 - pow({1}, 0.5 / max(1.0 - {0}, 1e-5))'.format(blend, dotnv) + + return '0.0' def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py index f68d426f..5e3e1e8d 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py @@ -94,15 +94,18 @@ def parse_tex_gradient(node: bpy.types.ShaderNodeTexGradient, out_socket: bpy.ty if grad == 'LINEAR': f = f'{co}.x' elif grad == 'QUADRATIC': - f = '0.0' + f = f'max({co}.x, 0.0)' + f = f'({f} * {f})' elif grad == 'EASING': - f = '0.0' + f = f'clamp({co}.x, 0.0, 1.0)' + f = f'({f} * {f} * (3.0 - 2.0 * {f}))' elif grad == 'DIAGONAL': f = f'({co}.x + {co}.y) * 0.5' elif grad == 'RADIAL': f = f'atan({co}.y, {co}.x) / PI2 + 0.5' elif grad == 'QUADRATIC_SPHERE': - f = '0.0' + f = f'max(1.0 - sqrt({co}.x * {co}.x + {co}.y * {co}.y + {co}.z * {co}.z), 0.0)' + f = f'({f} * {f})' else: # SPHERICAL f = f'max(1.0 - sqrt({co}.x * {co}.x + {co}.y * {co}.y + {co}.z * {co}.z), 0.0)' @@ -246,13 +249,13 @@ def parse_tex_magic(node: bpy.types.ShaderNodeTexMagic, out_socket: bpy.types.No co = 'bposition' scale = c.get_value_input(node, ['Scale']) + distortion = c.get_value_input(node, ['Distortion']) + depth = node.turbulence_depth - # Color if out_socket == node.outputs['Color']: - res = f'tex_magic({co} * {scale} * 4.0)' - # Fac + res = f'tex_magic({co} * {scale}, {distortion}, {depth})' else: - res = f'tex_magic_f({co} * {scale} * 4.0)' + res = f'tex_magic_f({co} * {scale}, {distortion}, {depth})' return res @@ -277,36 +280,70 @@ if bpy.app.version < (4, 1, 0): def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: c.write_procedurals() state.curshader.add_function(c_functions.str_tex_noise) - c.assets_add(os.path.join(lnx.utils.get_sdk_path(), 'leenkx', 'Assets', 'noise256.png')) - c.assets_add_embedded_data('noise256.png') - state.curshader.add_uniform('sampler2D snoise256', link='$noise256.png') - if node.inputs['Vector'].is_linked: - co = c.get_vector_input(node, ['Vector']) + + if 'Vector' in node.inputs and node.inputs['Vector'].is_linked: + co = c.parse_vector_input(node.inputs['Vector']) + elif node.inputs[0].is_linked: + co = c.parse_vector_input(node.inputs[0]) else: co = 'bposition' - scale = c.get_value_input(node, ['Scale']) - detail = c.get_value_input(node, ['Detail']) - roughness = c.get_value_input(node, ['Roughness']) - distortion = c.get_value_input(node, ['Distortion']) - if bpy.app.version >= (4, 1, 0): - if node.noise_type == "FBM": - state.curshader.add_function(c_functions.str_tex_musgrave) - if out_socket == node.outputs['Color']: - res = 'vec3(tex_musgrave_f({0} * {1}, {2}, {3}), tex_musgrave_f({0} * {1} + 120.0, {2}, {3}), tex_musgrave_f({0} * {1} + 168.0, {2}, {3}))'.format(co, scale, detail, distortion) - else: - res = f'tex_musgrave_f({co} * {scale} * 1.0, {detail}, {distortion})' + + w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0' + scale = c.parse_value_input(node.inputs['Scale']) if 'Scale' in node.inputs else '1.0' + detail = c.parse_value_input(node.inputs['Detail']) if 'Detail' in node.inputs else '2.0' + roughness = c.parse_value_input(node.inputs['Roughness']) if 'Roughness' in node.inputs else '0.5' + lacunarity = c.parse_value_input(node.inputs['Lacunarity']) if 'Lacunarity' in node.inputs else '2.0' + offset = c.parse_value_input(node.inputs['Offset']) if 'Offset' in node.inputs else '0.0' + gain = c.parse_value_input(node.inputs['Gain']) if 'Gain' in node.inputs else '1.0' + distortion = c.parse_value_input(node.inputs['Distortion']) if 'Distortion' in node.inputs else '0.0' + + dimensions = getattr(node, 'noise_dimensions', '3D') + noise_type = getattr(node, 'noise_type', 'FBM') + normalize = 'true' if getattr(node, 'normalize', True) else 'false' + + type_map = { + 'FBM': 'noise_fbm', + 'MULTIFRACTAL': 'noise_multi_fractal', + 'RIDGED_MULTIFRACTAL': 'noise_ridged_multi_fractal', + 'HYBRID_MULTIFRACTAL': 'noise_hybrid_multi_fractal', + 'HETERO_TERRAIN': 'noise_hetero_terrain' + } + func_name = type_map.get(noise_type, 'noise_fbm') + + is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color') + + if dimensions == '1D': + p_expr = f"({w}) * ({scale})" + dist_expr = f"({p_expr}) + snoise(({p_expr}) + random_float_offset(0.0)) * ({distortion})" if distortion != '0.0' else p_expr + if is_color: + res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(1.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_float_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" else: - if out_socket == node.outputs['Color']: - res = 'vec3(tex_noise({0} * {1},{2},{3}), tex_noise({0} * {1} + 120.0,{2},{3}), tex_noise({0} * {1} + 168.0,{2},{3}))'.format(co, scale, detail, distortion) - else: - res = 'tex_noise({0} * {1},{2},{3})'.format(co, scale, detail, distortion) - if node.normalize: - res = f'(1.0 - ({res}))' + res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})" + + elif dimensions == '2D': + p_expr = f"({co}).xy * ({scale})" + dist_expr = f"({p_expr}) + vec2(snoise(({p_expr}) + random_vec2_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec2_offset(1.0)) * ({distortion}))" if distortion != '0.0' else p_expr + if is_color: + res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(2.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec2_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" + else: + res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})" + + elif dimensions == '4D': + p_expr = f"vec4({co}, {w}) * ({scale})" + dist_expr = f"({p_expr}) + vec4(snoise(({p_expr}) + random_vec4_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(2.0)) * ({distortion}), snoise(({p_expr}) + random_vec4_offset(3.0)) * ({distortion}))" if distortion != '0.0' else p_expr + if is_color: + res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec4_offset(5.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" + else: + res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})" + else: - if out_socket == node.outputs['Color']: - res = 'vec3(tex_noise({0} * {1},{2},{3}), tex_noise({0} * {1} + 120.0,{2},{3}), tex_noise({0} * {1} + 168.0,{2},{3}))'.format(co, scale, detail, distortion) + p_expr = f"({co}) * ({scale})" + dist_expr = f"({p_expr}) + vec3(snoise(({p_expr}) + random_vec3_offset(0.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(1.0)) * ({distortion}), snoise(({p_expr}) + random_vec3_offset(2.0)) * ({distortion}))" if distortion != '0.0' else p_expr + if is_color: + res = f"vec3({func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(3.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}), {func_name}(({dist_expr}) + random_vec3_offset(4.0), clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize}))" else: - res = 'tex_noise({0} * {1},{2},{3})'.format(co, scale, detail, distortion) + res = f"{func_name}({dist_expr}, clamp({detail}, 0.0, 15.0), max({roughness}, 0.0), {lacunarity}, {offset}, {gain}, {normalize})" + return res if bpy.app.version < (5, 0, 0): @@ -459,13 +496,65 @@ def parse_sky_multiple_scattering(node: bpy.types.ShaderNodeTexSky, state: Parse def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - if state.context == ParserContext.OBJECT: - log.warn('Environment Texture node is not supported for object node trees, using default value') - return c.to_vec3([0.0, 0.0, 0.0]) - if node.image is None: return c.to_vec3([1.0, 0.0, 1.0]) + image = node.image + + # Object context: sample environment texture directly in material shader. + if state.context == ParserContext.OBJECT: + tex_store = c.store_var_name(node) + + if c.node_need_reevaluation_for_screenspace_derivative(node): + tex_store += state.get_parser_pass_suffix() + + if c.is_parsed(tex_store): + return f'{tex_store}.rgb' + + state.parsed.add(tex_store) + + tex_name = c.node_name(node.name) + tex_link = None + tex_default_file = None + is_lnx_mat_param = None + if node.lnx_material_param: + tex_link = node.name + is_lnx_mat_param = True + + tex = c.make_texture( + image, + tex_name, + c.mat_get_material(), + getattr(node, 'interpolation', 'Smart'), + getattr(node, 'extension', 'REPEAT') + ) + if tex is None: + log.warn(f'Object "{state.tree_name}": missing environment texture image "{node.name}"') + return c.to_vec3([1.0, 0.0, 1.0]) + + if is_lnx_mat_param is None: + c.mat_bind_texture(tex) + + state.con.add_elem('tex', 'short2norm') + state.curshader.add_uniform(f'sampler2D {tex_name}', link=tex_link, default_value=tex_default_file, is_lnx_mat_param=is_lnx_mat_param) + state.curshader.add_include('std/math.glsl') + + if node.inputs[0].is_linked: + co = c.parse_vector_input(node.inputs[0]) + else: + state.curshader.add_uniform('vec3 cameraPos', link='_cameraPosition') + co = 'reflect(normalize(wposition - cameraPos), n)' + + if node.projection == 'EQUIRECTANGULAR': + state.curshader.write(f'vec2 uv = envMapEquirect(normalize({co}));') + else: + state.curshader.write(f'vec2 uv = envMapMirror(normalize({co}));') + state.curshader.write(f'vec4 {tex_store} = textureLod({tex_name}, uv, 0.0);') + if image.colorspace_settings.name == 'sRGB': + state.curshader.write(f'{tex_store}.rgb = pow({tex_store}.rgb, vec3(2.2));') + + return f'{tex_store}.rgb' + world = state.world world.world_defs += '_EnvTex' @@ -474,7 +563,6 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: curshader.add_include('std/math.glsl') curshader.add_uniform('sampler2D envmap', link='_envmap') - image = node.image filepath = image.filepath if image.packed_file is None and not os.path.isfile(lnx.utils.asset_path(filepath)): @@ -509,7 +597,7 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: if do_convert: if not os.path.isfile(unpack_filepath): - lnx.utils.unpack_image(image, unpack_filepath, file_format=target_format) + lnx.utils.convert_image(image, unpack_filepath, target_format) elif not os.path.isfile(unpack_filepath) or os.path.getsize(unpack_filepath) != image.packed_file.size: with open(unpack_filepath, 'wb') as f: @@ -561,7 +649,15 @@ def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: wrd.world_defs += '_Rad' assets.add_khafile_def("lnx_radiance") - return 'texture(envmap, envMapEquirect(pos)).rgb * envmapStrength' + if node.inputs[0].is_linked: + co = c.parse_vector_input(node.inputs[0]) + else: + co = 'pos' + + if node.projection == 'EQUIRECTANGULAR': + return f'texture(envmap, envMapEquirect({co})).rgb * envmapStrength' + else: + return f'texture(envmap, envMapMirror({co})).rgb * envmapStrength' def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: @@ -570,6 +666,9 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type outp = 1 elif out_socket.type == 'VECTOR': outp = 2 + elif out_socket.name == 'W': + outp = 3 + m = 0 if node.distance == 'MANHATTAN': m = 1 @@ -577,35 +676,50 @@ def parse_tex_voronoi(node: bpy.types.ShaderNodeTexVoronoi, out_socket: bpy.type m = 2 elif node.distance == 'MINKOWSKI': m = 3 - # TODO: Add node.distance == 'MANHATHAN' - # Add node.feature - # Add node.voronoi_dimensions + exp = c.get_value_input(node, ['Exponent']) + + f = 0 + if node.feature == 'F2': + f = 1 + elif node.feature == 'SMOOTH_F1': + f = 2 + elif node.feature == 'DISTANCE_TO_EDGE': + f = 3 + elif node.feature == 'N_SPHERE_RADIUS': + f = 4 + + dim = node.voronoi_dimensions + normalize = 1 if node.normalize else 0 c.write_procedurals() - state.curshader.add_function(c_functions.str_tex_voronoi) + state.curshader.add_function(getattr(c_functions, f'str_tex_voronoi_{bpy.app.version[0]}')) if node.inputs['Vector'].is_linked: co = c.get_vector_input(node, ['Vector']) else: co = 'bposition' - scale = c.get_value_input(node, ['Scale']) - exp = c.get_value_input(node, ['Exponent']) if m == 3 else '1.0' - randomness = c.get_value_input(node, ['Randomness']) + w = c.get_value_input(node, ['W']) if 'W' in node.inputs else '0.0' + scale = c.get_value_input(node, ['Scale']) if 'Scale' in node.inputs else '5.0' + detail = c.get_value_input(node, ['Detail']) if 'Detail' in node.inputs else '0.0' + roughness = c.get_value_input(node, ['Roughness']) if 'Roughness' in node.inputs else '0.5' + lacunarity = c.get_value_input(node, ['Lacunarity']) if 'Lacunarity' in node.inputs else '2.0' + smoothness = c.get_value_input(node, ['Smoothness']) if 'Smoothness' in node.inputs else '1.0' + randomness = c.get_value_input(node, ['Randomness']) if 'Randomness' in node.inputs else '1.0' - # Color or Position if out_socket == node.outputs['Color'] or out_socket == node.outputs['Position']: - res = 'tex_voronoi({0}, {1}, {2}, {3}, {4}, {5})'.format(co, randomness, m, outp, scale, exp) - # Distance + res = 'tex_voronoi_{0}({1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13})'.format(dim.lower(), co, randomness, m, outp, scale, exp, w, detail, roughness, lacunarity, smoothness, f, normalize) else: - res = 'tex_voronoi({0}, {1}, {2}, {3}, {4}, {5}).x'.format(co, randomness, m, outp, scale, exp) + res = 'tex_voronoi_{0}({1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}).x'.format(dim.lower(), co, randomness, m, outp, scale, exp, w, detail, roughness, lacunarity, smoothness, f, normalize) return res def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: c.write_procedurals() + state.curshader.add_function(c_functions.str_tex_noise) state.curshader.add_function(c_functions.str_tex_wave) + if node.inputs['Vector'].is_linked: co = c.get_vector_input(node, ['Vector']) else: @@ -614,22 +728,101 @@ def parse_tex_wave(node: bpy.types.ShaderNodeTexWave, out_socket: bpy.types.Node distortion = c.get_value_input(node, ['Distortion']) detail = c.get_value_input(node, ['Detail']) detail_scale = c.get_value_input(node, ['Detail Scale']) + detail_roughness = c.get_value_input(node, ['Detail Roughness']) phase_offset = c.get_value_input(node, ['Phase Offset']) + wave_type = 0 if node.wave_type == 'BANDS' else 1 + + dir_map = {'X': 0, 'Y': 1, 'Z': 2, 'DIAGONAL': 3} + + if hasattr(node, 'wave_direction'): + wave_dir = dir_map.get(node.wave_direction, 0) + elif wave_type == 0: + wave_dir = dir_map.get(node.bands_direction, 0) + else: + wave_dir = dir_map.get(node.rings_direction, 0) + if node.wave_profile == 'SIN': wave_profile = 0 - else: + elif node.wave_profile == 'SAW': wave_profile = 1 - if node.wave_type == 'BANDS': - wave_type = 0 else: - wave_type = 1 + wave_profile = 2 + + args = '{0} * {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}'.format( + co, scale, wave_type, wave_dir, wave_profile, distortion, detail, detail_scale, phase_offset, detail_roughness + ) - # Color if out_socket == node.outputs['Color']: - res = 'vec3(tex_wave_f({0} * {1},{2},{3},{4},{5},{6},{7}))'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale, phase_offset) - # Fac + res = 'vec3(tex_wave_f({0}))'.format(args) else: - res = 'tex_wave_f({0} * {1},{2},{3},{4},{5},{6},{7})'.format(co, scale, wave_type, wave_profile, distortion, detail, detail_scale, phase_offset) + res = 'tex_wave_f({0})'.format(args) return res + +def parse_tex_gabor(node: bpy.types.ShaderNodeTexGabor, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: + c.write_procedurals() + state.curshader.add_function(c_functions.str_tex_gabor) + + if node.inputs['Vector'].is_linked: + co = c.get_vector_input(node, ['Vector']) + else: + co = 'bposition' + scale = c.get_value_input(node, ['Scale']) + freq = c.get_value_input(node, ['Frequency']) + anisotropy = c.get_value_input(node, ['Anisotropy']) + + gabor_type = '0.0' if node.gabor_type == '2D' else '1.0' + + if node.gabor_type == '2D': + orientation_2d = c.get_value_input(node, ['Orientation']) + orientation_3d = 'vec3(0.0)' + else: + orientation_2d = '0.0' + orientation_3d = c.get_vector_input(node, ['Orientation']) + + args = '{0}, {1}, {2}, {3}, {4}, {5}, {6}'.format( + co, scale, freq, anisotropy, orientation_2d, orientation_3d, gabor_type + ) + + if out_socket == node.outputs['Phase']: + return 'tex_gabor_phase({0})'.format(args) + elif out_socket == node.outputs['Intensity']: + return 'tex_gabor_intensity({0})'.format(args) + + return 'tex_gabor_value({0})'.format(args) + + +def parse_tex_white_noise(node: bpy.types.ShaderNodeTexWhiteNoise, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: + c.write_procedurals() + state.curshader.add_function(c_functions.str_tex_noise) + + if node.inputs[0].is_linked: + co = c.parse_vector_input(node.inputs[0]) + else: + co = 'bposition' + + w = c.parse_value_input(node.inputs['W']) if 'W' in node.inputs else '0.0' + + dimensions = getattr(node, 'noise_dimensions', '3D') + is_color = (out_socket == node.outputs[1]) or (getattr(out_socket, 'name', '') == 'Color') + + if dimensions == '1D': + if is_color: + return f'hash_float_to_vec3({w})' + return f'hash_float_to_float({w})' + + elif dimensions == '2D': + if is_color: + return f'hash_vec2_to_vec3(({co}).xy)' + return f'hash_vec2_to_float(({co}).xy)' + + elif dimensions == '4D': + if is_color: + return f'hash_vec4_to_vec3(vec4({co}, {w}))' + return f'hash_vec4_to_float(vec4({co}, {w}))' + + else: + if is_color: + return f'hash_vec3_to_vec3({co})' + return f'hash_vec3_to_float({co})' diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py index bef69980..2be5f4ce 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py @@ -29,11 +29,14 @@ def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types. vec = c.get_vector_input(node, ['Vector']) curves = node.mapping.curves name = c.node_name(node.name) - # mapping.curves[0].points[0].handle_type # bezier curve - return '(vec3({0}, {1}, {2}) * {3})'.format( - c.vector_curve(name + '0', vec + '.x', curves[0].points), - c.vector_curve(name + '1', vec + '.y', curves[1].points), - c.vector_curve(name + '2', vec + '.z', curves[2].points), fac) + + res_x = c.vector_curve(name + '0', vec + '.x', curves[0].points) + res_y = c.vector_curve(name + '1', vec + '.y', curves[1].points) + res_z = c.vector_curve(name + '2', vec + '.z', curves[2].points) + + res_vec = f'vec3({res_x}, {res_y}, {res_z})' + + return f'mix({vec}, {res_vec}, {fac})' def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: @@ -43,9 +46,8 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, # Interpolation strength strength = c.get_value_input(node, ['Strength']) - # distance = c.get_value_input(node, ['Distance']) + distance = c.get_value_input(node, ['Distance']) height = c.get_value_input(node, ['Height']) - # normal = c.get_vector_input(node, ['Normal']) state.current_pass = ParserPass.DX_SCREEN_SPACE height_dx = c.get_value_input(node, ['Height']) @@ -53,9 +55,11 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, height_dy = c.get_value_input(node, ['Height']) state.current_pass = ParserPass.REGULAR + nor = c.get_vector_input(node, ['Normal']) if node.inputs['Normal'].is_linked else 'n' + if height_dx != height or height_dy != height: - tangent = f'{c.dfdx_fine("wposition")} + n * ({height_dx} - {height})' - bitangent = f'{c.dfdy_fine("wposition")} + n * ({height_dy} - {height})' + tangent = f'{c.dfdx_fine("wposition")} + {nor} * (({height_dx} - {height}) * {distance})' + bitangent = f'{c.dfdy_fine("wposition")} + {nor} * (({height_dy} - {height}) * {distance})' # Cross-product operand order, dFdy is flipped on d3d11 bitangent_first = utils.get_gapi() == 'direct3d11' @@ -66,73 +70,58 @@ def parse_bump(node: bpy.types.ShaderNodeBump, out_socket: bpy.types.NodeSocket, if bitangent_first: # We need to normalize twice, once for the correct "weight" of the strength, # once for having a normalized output vector (lerping vectors does not preserve magnitude) - res = f'normalize(mix(n, normalize(cross({bitangent}, {tangent})), {strength}))' + res = f'normalize(mix({nor}, normalize(cross({bitangent}, {tangent})), {strength}))' else: - res = f'normalize(mix(n, normalize(cross({tangent}, {bitangent})), {strength}))' + res = f'normalize(mix({nor}, normalize(cross({tangent}, {bitangent})), {strength}))' else: - res = 'n' + res = nor return res def parse_mapping(node: bpy.types.ShaderNodeMapping, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - # TODO: Add support for "Normal" type - # Only "Point", "Texture" and "Vector" types supported for now.. - # More information about the order of operations for this node: - # https://docs.blender.org/manual/en/latest/render/shader_nodes/vector/mapping.html#properties + input_vector = node.inputs['Vector'] + input_location = node.inputs['Location'] + input_rotation = node.inputs['Rotation'] + input_scale = node.inputs['Scale'] - input_vector: bpy.types.NodeSocket = node.inputs['Vector'] - input_location: bpy.types.NodeSocket = node.inputs['Location'] - input_rotation: bpy.types.NodeSocket = node.inputs['Rotation'] - input_scale: bpy.types.NodeSocket = node.inputs['Scale'] out = c.parse_vector_input(input_vector) if input_vector.is_linked else c.to_vec3(input_vector.default_value) location = c.parse_vector_input(input_location) if input_location.is_linked else c.to_vec3(input_location.default_value) rotation = c.parse_vector_input(input_rotation) if input_rotation.is_linked else c.to_vec3(input_rotation.default_value) scale = c.parse_vector_input(input_scale) if input_scale.is_linked else c.to_vec3(input_scale.default_value) - # Use inner functions because the order of operations varies between - # mapping node vector types. This adds a slight overhead but makes - # the code much more readable. - # - "Point" and "Vector" use Scale -> Rotate -> Translate - # - "Texture" uses Translate -> Rotate -> Scale - def calc_location(output: str) -> str: - # Vectors and Eulers support the "!=" operator - if input_scale.is_linked or input_scale.default_value != Vector((1, 1, 1)): - if node.vector_type == 'TEXTURE': - output = f'({output} / {scale})' - else: - output = f'({output} * {scale})' + if node.vector_type == 'TEXTURE': + if input_location.is_linked or any(v != 0.0 for v in input_location.default_value): + out = f"({out} - {location})" - return output + if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value): + var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() + state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));") + state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));") + state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);") + out = f"({out} * {var_name}Z * {var_name}Y * {var_name}X)" - def calc_scale(output: str) -> str: - if input_location.is_linked or input_location.default_value != Vector((0, 0, 0)): - # z location is a little off sometimes?... - if node.vector_type == 'TEXTURE': - output = f'({output} - {location})' - else: - output = f'({output} + {location})' - return output + if input_scale.is_linked or any(v != 1.0 for v in input_scale.default_value): + out = f"({out} / {scale})" - out = calc_location(out) if node.vector_type == 'TEXTURE' else calc_scale(out) + elif node.vector_type in ['POINT', 'VECTOR', 'NORMAL']: + if input_scale.is_linked or any(v != 1.0 for v in input_scale.default_value): + out = f"({out} * {scale})" - if input_rotation.is_linked or input_rotation.default_value != Euler((0, 0, 0)): - var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() - if node.vector_type == 'TEXTURE': - state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), sin({rotation}.x), 0.0, -sin({rotation}.x), cos({rotation}.x));') - state.curshader.write(f'mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, -sin({rotation}.y), 0.0, 1.0, 0.0, sin({rotation}.y), 0.0, cos({rotation}.y));') - state.curshader.write(f'mat3 {var_name}Z = mat3(cos({rotation}.z), sin({rotation}.z), 0.0, -sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);') - else: - # A little bit redundant, but faster than 12 more multiplications to make it work dynamically - state.curshader.write(f'mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos(-{rotation}.x), sin(-{rotation}.x), 0.0, -sin(-{rotation}.x), cos(-{rotation}.x));') - state.curshader.write(f'mat3 {var_name}Y = mat3(cos(-{rotation}.y), 0.0, -sin(-{rotation}.y), 0.0, 1.0, 0.0, sin(-{rotation}.y), 0.0, cos(-{rotation}.y));') - state.curshader.write(f'mat3 {var_name}Z = mat3(cos(-{rotation}.z), sin(-{rotation}.z), 0.0, -sin(-{rotation}.z), cos(-{rotation}.z), 0.0, 0.0, 0.0, 1.0);') + if input_rotation.is_linked or any(v != 0.0 for v in input_rotation.default_value): + var_name = c.node_name(node.name) + "_rotation" + state.get_parser_pass_suffix() + state.curshader.write(f"mat3 {var_name}X = mat3(1.0, 0.0, 0.0, 0.0, cos({rotation}.x), -sin({rotation}.x), 0.0, sin({rotation}.x), cos({rotation}.x));") + state.curshader.write(f"mat3 {var_name}Y = mat3(cos({rotation}.y), 0.0, sin({rotation}.y), 0.0, 1.0, 0.0, -sin({rotation}.y), 0.0, cos({rotation}.y));") + state.curshader.write(f"mat3 {var_name}Z = mat3(cos({rotation}.z), -sin({rotation}.z), 0.0, sin({rotation}.z), cos({rotation}.z), 0.0, 0.0, 0.0, 1.0);") + out = f"({out} * {var_name}X * {var_name}Y * {var_name}Z)" - # XYZ-order euler rotation - out = f'{out} * {var_name}X * {var_name}Y * {var_name}Z' + if node.vector_type == 'POINT': + if input_location.is_linked or any(v != 0.0 for v in input_location.default_value): + out = f"({out} + {location})" - out = calc_scale(out) if node.vector_type == 'TEXTURE' else calc_location(out) + if node.vector_type == 'NORMAL': + out = f"normalize({out})" return out @@ -152,58 +141,124 @@ def parse_normalmap(node: bpy.types.ShaderNodeNormalMap, out_socket: bpy.types.N if state.curshader == state.tese: return c.get_vector_input(node, ["Normal"]) else: - # TODO: - # space = node.space - # map = node.uv_map - # Color - c.parse_normal_map_color_input(node.inputs['Color'], node.inputs['Strength']) + c.parse_normal_map_color_input(node.inputs['Color'], node.inputs['Strength'], space=node.space) return 'n' def parse_vectortransform(node: bpy.types.ShaderNodeVectorTransform, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - # TODO: - # vector_type = node.vector_type - # conv_from = node.convert_from - # conv_to = node.convert_to - # Pass through - return c.get_vector_input(node, ['Vector']) + vec = c.get_vector_input(node, ['Vector']) + v_type = node.vector_type + v_from = node.convert_from + v_to = node.convert_to + + if v_from == v_to: + return vec + + shader = state.curshader + + if v_from == 'OBJECT' or v_to == 'OBJECT': + shader.add_uniform('mat4 W', link='_worldMatrix') + shader.add_uniform('mat4 IW', link='_inverseWorldMatrix') + if v_from == 'CAMERA' or v_to == 'CAMERA': + shader.add_uniform('mat4 V', link='_viewMatrix') + shader.add_uniform('mat4 IV', link='_inverseViewMatrix') + + w = '1.0' if v_type == 'POINT' else '0.0' + res = f'vec4({vec}, {w})' + + if v_from == 'OBJECT': + shader.write('mat4 Wn = W;') + shader.write('Wn[0] = normalize(Wn[0]);') + shader.write('Wn[1] = normalize(Wn[1]);') + shader.write('Wn[2] = normalize(Wn[2]);') + res = f'(Wn * {res})' + elif v_from == 'CAMERA': + res = f'(IV * {res})' + + if v_to == 'OBJECT': + shader.write('mat4 IWn = IW;') + shader.write('IWn[0] = normalize(IWn[0]);') + shader.write('IWn[1] = normalize(IWn[1]);') + shader.write('IWn[2] = normalize(IWn[2]);') + res = f'(IWn * {res})' + elif v_to == 'CAMERA': + res = f'(V * {res})' + + out = f'({res}).xyz' + + if v_type == 'NORMAL': + out = f'normalize({out})' + + return out def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - # TODO: - # space = node.space height = c.get_value_input(node, ['Height']) midlevel = c.get_value_input(node, ['Midlevel']) scale = c.get_value_input(node, ['Scale']) - nor = c.get_vector_input(node, ['Normal']) - return f'(vec3({height}) * {scale})' + + if node.inputs['Normal'].is_linked: + nor = c.get_vector_input(node, ['Normal']) + else: + nor = 'wnormal' + + if node.space == 'OBJECT': + nor = f'(inverse(mat3(W)) * {nor})' + + disp = f'normalize({nor}) * (vec3({height}) - vec3({midlevel})) * {scale}' + + if node.space == 'OBJECT': + return f'(vec4(W * vec4({disp}, 0.0)).xyz)' + else: + return f'({disp})' + + +def parse_vector_displacement(node: bpy.types.ShaderNodeVectorDisplacement, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: + vector = c.get_vector_input(node, ['Vector']) + midlevel = c.get_value_input(node, ['Midlevel']) + scale = c.get_value_input(node, ['Scale']) + + offset = f'(({vector} - vec3({midlevel})) * {scale})' + + if node.space == 'TANGENT': + t_obj = 'normalize(inverse(mat3(W)) * wtangent)' + n_obj = 'normalize(inverse(mat3(W)) * wnormal)' + b_obj = f'normalize(cross({n_obj}, {t_obj}))' + disp = f'({t_obj} * {offset}.x + {n_obj} * {offset}.y + {b_obj} * {offset}.z)' + return f'(vec4(W * vec4({disp}, 0.0)).xyz)' + elif node.space == 'OBJECT': + return f'(vec4(W * vec4({offset}, 0.0)).xyz)' + else: + return offset def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: type = node.rotation_type - input_vector: bpy.types.NodeSocket = c.get_vector_input(node, ['Vector']) - input_center: bpy.types.NodeSocket = c.get_vector_input(node, ['Center']) - input_axis: bpy.types.NodeSocket = c.get_vector_input(node, ['Axis']) - input_angle: bpy.types.NodeSocket = c.get_value_input(node, ['Angle']) - input_rotation: bpy.types.NodeSocket = c.get_vector_input(node, ['Rotation']) - - if node.invert: - input_invert = "0" - else: - input_invert = "1" + input_vector = c.get_vector_input(node, ['Vector']) + input_center = c.get_vector_input(node, ['Center']) + input_axis = c.get_vector_input(node, ['Axis']) + input_angle = c.get_value_input(node, ['Angle']) + input_rotation = c.get_vector_input(node, ['Rotation']) + inv = "-1.0" if node.invert else "1.0" + state.curshader.add_function(c_functions.str_rotate_around_axis) if type == 'AXIS_ANGLE': - return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )' + return f'vec3( (length({input_axis}) > 0.001) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {inv}) + {input_center} : {input_vector} )' + elif type == 'X_AXIS': - return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {inv}) + {input_center} )' + elif type == 'Y_AXIS': - return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {inv}) + {input_center} )' + elif type == 'Z_AXIS': - return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {inv}) + {input_center} )' + elif type == 'EULER_XYZ': state.curshader.add_function(c_functions.str_euler_to_mat3) - return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' + rot_val = f'({input_rotation} * {inv})' + return f'vec3( euler_to_mat3({rot_val}) * ({input_vector} - {input_center}) + {input_center})' - return f'(vec3(1.0, 0.0, 0.0))' + return f'vec3(0.0, 0.0, 0.0)' diff --git a/leenkx/blender/lnx/material/lnx_nodes/shader_data_node.py b/leenkx/blender/lnx/material/lnx_nodes/shader_data_node.py index b3a50b24..107cbd89 100644 --- a/leenkx/blender/lnx/material/lnx_nodes/shader_data_node.py +++ b/leenkx/blender/lnx/material/lnx_nodes/shader_data_node.py @@ -53,7 +53,6 @@ class ShaderDataNode(Node): def draw_buttons(self, context, layout): col = layout.column(align=True) col.label(text="Input Type:") - # Use a row to expand horizontally col.row().prop(self, "input_type", expand=True) split = layout.split(factor=0.5, align=True) @@ -72,13 +71,18 @@ class ShaderDataNode(Node): def init(self, context): self.outputs.new('NodeSocketColor', 'Color') self.outputs.new('NodeSocketVector', 'Vector') - self.outputs.new('NodeSocketFloat', 'Float') - self.outputs.new('NodeSocketInt', 'Int') + self.outputs.new('NodeSocketFloat', 'Float/Int') def __parse(self, out_socket: NodeSocket, state: ParserState) -> str: if self.input_type == "uniform": - state.frag.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name) - state.vert.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name) + if self.variable_type == "vec4": + state.frag.add_uniform(f'vec3 {self.variable_name}_xyz', link=f'{self.variable_name}_xyz') + state.frag.add_uniform(f'float {self.variable_name}_w', link=f'{self.variable_name}_w') + state.vert.add_uniform(f'vec3 {self.variable_name}_xyz', link=f'{self.variable_name}_xyz') + state.vert.add_uniform(f'float {self.variable_name}_w', link=f'{self.variable_name}_w') + else: + state.frag.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name) + state.vert.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name) if self.variable_type == "sampler2D": state.frag.add_uniform('vec2 screenSize', link='_screenSize') @@ -87,20 +91,41 @@ class ShaderDataNode(Node): if self.variable_type == "vec2": return f'vec3({self.variable_name}.xy, 0)' + if self.variable_type == "vec4": + if out_socket == self.outputs[2]: + return f'{self.variable_name}_w' + else: + return f'{self.variable_name}_xyz' + return self.variable_name else: - if self.input_source == "frag": - state.frag.add_in(f'{self.variable_type} {self.variable_name}') - return self.variable_name + if self.variable_type == "vec4": + if self.input_source == "frag": + state.frag.add_in(f'vec3 {self.variable_name}_xyz') + state.frag.add_in(f'float {self.variable_name}_w') + else: + state.vert.add_out(f'vec3 out_{self.variable_name}_xyz') + state.frag.add_in(f'vec3 out_{self.variable_name}_xyz') + state.vert.write(f'out_{self.variable_name}_xyz = {self.variable_name}_xyz;') - # Reroute input from vertex shader to fragment shader (input must exist!) + state.vert.add_out(f'float out_{self.variable_name}_w') + state.frag.add_in(f'float out_{self.variable_name}_w') + state.vert.write(f'out_{self.variable_name}_w = {self.variable_name}_w;') + + if out_socket == self.outputs[2]: + return f'out_{self.variable_name}_w' if self.input_source != "frag" else f'{self.variable_name}_w' + else: + return f'out_{self.variable_name}_xyz' if self.input_source != "frag" else f'{self.variable_name}_xyz' else: - state.vert.add_out(f'{self.variable_type} out_{self.variable_name}') - state.frag.add_in(f'{self.variable_type} out_{self.variable_name}') - - state.vert.write(f'out_{self.variable_name} = {self.variable_name};') - return 'out_' + self.variable_name + if self.input_source == "frag": + state.frag.add_in(f'{self.variable_type} {self.variable_name}') + return self.variable_name + else: + state.vert.add_out(f'{self.variable_type} out_{self.variable_name}') + state.frag.add_in(f'{self.variable_type} out_{self.variable_name}') + state.vert.write(f'out_{self.variable_name} = {self.variable_name};') + return 'out_' + self.variable_name @staticmethod def parse(node: 'ShaderDataNode', out_socket: NodeSocket, state: ParserState) -> str: diff --git a/leenkx/blender/lnx/material/make.py b/leenkx/blender/lnx/material/make.py index 7777ba80..5ab27330 100644 --- a/leenkx/blender/lnx/material/make.py +++ b/leenkx/blender/lnx/material/make.py @@ -305,7 +305,7 @@ def parse(material: Material, mat_data, mat_users: Dict[Material, List[Object]], if wrd.lnx_single_data_file: mat_data['shader'] = shader_data_name else: - # Make sure that custom materials are not expected to be in .arm format + # Make sure that custom materials are not expected to be in .lnx format ext = '' if wrd.lnx_minimize and material.lnx_custom_material == "" else '.json' mat_data['shader'] = shader_data_name + ext + '/' + shader_data_name diff --git a/leenkx/blender/lnx/material/make_attrib.py b/leenkx/blender/lnx/material/make_attrib.py index 63c44a16..c593b56e 100644 --- a/leenkx/blender/lnx/material/make_attrib.py +++ b/leenkx/blender/lnx/material/make_attrib.py @@ -112,7 +112,7 @@ def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor if is_morph: - make_morph_target.morph_pos(vert) + make_morph_target.morph_pos(con_mesh, vert) if is_bone: @@ -124,7 +124,7 @@ def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor if is_morph: - make_morph_target.morph_nor(vert, is_bone, prep) + make_morph_target.morph_nor(con_mesh, vert, is_bone, prep) if is_bone: diff --git a/leenkx/blender/lnx/material/make_depth.py b/leenkx/blender/lnx/material/make_depth.py index 95430245..e04ac0d7 100644 --- a/leenkx/blender/lnx/material/make_depth.py +++ b/leenkx/blender/lnx/material/make_depth.py @@ -100,7 +100,7 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False): frag.write('float ior;') if(con_depth).is_elem('morph'): - make_morph_target.morph_pos(vert) + make_morph_target.morph_pos(con_depth, vert) if con_depth.is_elem('bone'): make_skin.skin_pos(vert) @@ -187,11 +187,11 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False): if shadowmap: tese.add_uniform('mat4 LVP', '_lightViewProjectionMatrix') - tese.write('wposition += wnormal * disp;') + tese.write('wposition += disp;') tese.write('gl_Position = LVP * vec4(wposition, 1.0);') else: tese.add_uniform('mat4 VP', '_viewProjectionMatrix') - tese.write('wposition += wnormal * disp;') + tese.write('wposition += disp;') tese.write('gl_Position = VP * vec4(wposition, 1.0);') # No displacement else: diff --git a/leenkx/blender/lnx/material/make_finalize.py b/leenkx/blender/lnx/material/make_finalize.py index 336980f3..f3876dcc 100644 --- a/leenkx/blender/lnx/material/make_finalize.py +++ b/leenkx/blender/lnx/material/make_finalize.py @@ -23,32 +23,31 @@ def make(con_mesh: ShaderContext): # Additional values referenced in cycles # TODO: enable from cycles.py + if (frag.contains('dotNV') or frag.contains('n')) and not frag.contains('vec3 n'): + vert.add_out('vec3 wnormal') + billboard = mat_state.material.lnx_billboard + if billboard == 'spherical': + vert.add_uniform('mat3 N', '_normalMatrixSphere') + elif billboard == 'cylindrical': + vert.add_uniform('mat3 N', '_normalMatrixCylinder') + else: + vert.add_uniform('mat3 N', '_normalMatrix') + vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));') + frag.write_attrib('vec3 n = normalize(wnormal);') + + # If not yet added, add nor vertex data + vertex_elems = con_mesh.data['vertex_elements'] + has_normals = False + for elem in vertex_elems: + if elem['name'] == 'nor': + has_normals = True + break + if not has_normals: + vertex_elems.append({'name': 'nor', 'data': 'short2norm'}) + if frag.contains('dotNV') and not frag.contains('float dotNV'): frag.write_init('float dotNV = max(dot(n, vVec), 0.0);') - # n is not always defined yet (in some shadowmap shaders e.g.) - if not frag.contains('vec3 n'): - vert.add_out('vec3 wnormal') - billboard = mat_state.material.lnx_billboard - if billboard == 'spherical': - vert.add_uniform('mat3 N', '_normalMatrixSphere') - elif billboard == 'cylindrical': - vert.add_uniform('mat3 N', '_normalMatrixCylinder') - else: - vert.add_uniform('mat3 N', '_normalMatrix') - vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));') - frag.write_attrib('vec3 n = normalize(wnormal);') - - # If not yet added, add nor vertex data - vertex_elems = con_mesh.data['vertex_elements'] - has_normals = False - for elem in vertex_elems: - if elem['name'] == 'nor': - has_normals = True - break - if not has_normals: - vertex_elems.append({'name': 'nor', 'data': 'short2norm'}) - write_wpos = False export_wpos = False if frag.contains('wposition') and not frag.contains('vec3 wposition'): diff --git a/leenkx/blender/lnx/material/make_inst.py b/leenkx/blender/lnx/material/make_inst.py index a3d77f2d..97436da5 100644 --- a/leenkx/blender/lnx/material/make_inst.py +++ b/leenkx/blender/lnx/material/make_inst.py @@ -9,15 +9,19 @@ def inst_pos(con, vert): vert.write('float srotz = sin(irot.z);') vert.write('float crotz = cos(irot.z);') vert.write('mat3 mirot = mat3(') - vert.write(' croty * crotz, srotz, -sroty * crotz,') - vert.write(' -croty * srotz * crotx + sroty * srotx, crotz * crotx, sroty * srotz * crotx + croty * srotx,') - vert.write(' croty * srotz * srotx + sroty * crotx, -crotz * srotx, -sroty * srotz * srotx + croty * crotx') + if con.material.lnx_instanced_rot_type == 'Local': + vert.write(' croty * crotz, srotz, -sroty * crotz,') + vert.write(' -croty * srotz * crotx + sroty * srotx, crotz * crotx, sroty * srotz * crotx + croty * srotx,') + vert.write(' croty * srotz * srotx + sroty * crotx, -crotz * srotx, -sroty * srotz * srotx + croty * crotx') + else: + vert.write(' croty * crotz, croty * srotz, -sroty,') + vert.write(' srotx * sroty * crotz - crotx * srotz, srotx * sroty * srotz + crotx * crotz, srotx * croty,') + vert.write(' crotx * sroty * crotz + srotx * srotz, crotx * sroty * srotz - srotx * crotz, crotx * croty') vert.write(');') vert.write('spos.xyz = mirot * spos.xyz;') if (con.data['name'] == 'mesh' or con.data['name'] == 'translucent' or con.data['name'] == 'refraction') and vert.contains('wnormal'): vert.write('wnormal = normalize(N * mirot * vec3(nor.xy, pos.w));') - if con.is_elem('iscl'): vert.write('spos.xyz *= iscl;') diff --git a/leenkx/blender/lnx/material/make_mesh.py b/leenkx/blender/lnx/material/make_mesh.py index 811338ba..3bb518f5 100644 --- a/leenkx/blender/lnx/material/make_mesh.py +++ b/leenkx/blender/lnx/material/make_mesh.py @@ -90,6 +90,10 @@ def make(context_id, rpasses): elif rid == 'Raytracer': make_raytracer(con_mesh) + if mat_state.material.lnx_solid: + if rid == 'Forward': + con_mesh.frag.write('fragColor[0].rgb = mix(basecol, fragColor[0].rgb, mixColor);') + make_finalize.make(con_mesh) assets.vs_equal(con_mesh, assets.shader_cons['mesh_vert']) @@ -107,6 +111,10 @@ def make_base(con_mesh, parse_opacity): tesc = None tese = None + if mat_state.material.lnx_solid: + if lnx.utils.get_rp().rp_renderer == 'Forward': + frag.add_uniform('float mixColor', '_mixColor') + billboard = mat_state.material.lnx_billboard if billboard == 'spherical': vert.add_uniform('mat3 N', '_normalMatrixSphere') @@ -192,7 +200,7 @@ def make_base(con_mesh, parse_opacity): if(con_mesh.is_elem('ipos')): vert.write('wposition = vec4(W * spos).xyz;') sh.add_uniform('mat4 VP', '_viewProjectionMatrix') - sh.write('wposition += wnormal * disp;') + sh.write('wposition += disp;') sh.write('gl_Position = VP * vec4(wposition, 1.0);') diff --git a/leenkx/blender/lnx/material/make_morph_target.py b/leenkx/blender/lnx/material/make_morph_target.py index 16c233a2..8d736219 100644 --- a/leenkx/blender/lnx/material/make_morph_target.py +++ b/leenkx/blender/lnx/material/make_morph_target.py @@ -5,7 +5,7 @@ if lnx.is_reload(__name__): else: lnx.enable_reload(__name__) -def morph_pos(vert): +def morph_pos(con, vert): if vert.has_attrib('vec2 texCoordMorph = morph * texUnpack;'): return rpdat = lnx.utils.get_rp() @@ -20,13 +20,19 @@ def morph_pos(vert): vert.add_uniform('float posUnpack', link='_posUnpack') vert.write_attrib('vec2 texCoordMorph = morph * texUnpack;') vert.write_attrib('spos.xyz *= posUnpack;') - vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz);') + if con.is_elem('imorph'): + vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz, imorph);') + else: + vert.write_attrib('getMorphedVertex(texCoordMorph, spos.xyz);') vert.write_attrib('spos.xyz /= posUnpack;') -def morph_nor(vert, is_bone, prep): +def morph_nor(con, vert, is_bone, prep): if vert.has_attrib('vec3 morphNor = vec3(0, 0, 0);'): return vert.write_attrib('vec3 morphNor = vec3(0, 0, 0);') - vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor);') + if con.is_elem('imorph'): + vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor, imorph);') + else: + vert.write_attrib('getMorphedNormal(texCoordMorph, vec3(nor.xy, pos.w), morphNor);') if not is_bone: vert.write_attrib(prep + 'wnormal = normalize(N * morphNor);') diff --git a/leenkx/blender/lnx/material/make_particle.py b/leenkx/blender/lnx/material/make_particle.py index 5ec7ef33..6a306fe7 100644 --- a/leenkx/blender/lnx/material/make_particle.py +++ b/leenkx/blender/lnx/material/make_particle.py @@ -22,35 +22,41 @@ def write(vert, particle_info=None, shadowmap=False): rotation_factor_random = 0 phase_factor = 0 phase_factor_random = 0 + normal_factor = 0.0 for obj in bpy.data.objects: for psys in obj.particle_systems: psettings = psys.settings if psettings.instance_object: - if psettings.instance_object.active_material: - # FIXME: Different particle systems may share the same particle object. This ideally should check the correct `ParticleSystem` using an id or name in the particle's object material. - if psettings.instance_object.active_material.name.replace(".", "_") == vert.context.matname: - # Rotation data - use_rotations = psettings.use_rotations - rotation_mode = psettings.rotation_mode - rotation_factor_random = psettings.rotation_factor_random - phase_factor = psettings.phase_factor - phase_factor_random = psettings.phase_factor_random + mat_found = False + for slot in psettings.instance_object.material_slots: + if slot.material and slot.material.name.replace(".", "_") == vert.context.matname: + mat_found = True + break + if mat_found: + # Rotation data + use_rotations = psettings.use_rotations + rotation_mode = psettings.rotation_mode + rotation_factor_random = psettings.rotation_factor_random + phase_factor = psettings.phase_factor + phase_factor_random = psettings.phase_factor_random + + normal_factor = psettings.normal_factor - # Texture slots data - if psettings.texture_slots and len(psettings.texture_slots.items()) != 0: - for tex_slot in psettings.texture_slots: - if not tex_slot: break - if not tex_slot.use_map_size: break # TODO: check also for other influences - if tex_slot.texture.use_color_ramp: - if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements: - ramp_el_len = len(tex_slot.texture.color_ramp.elements.items()) - for element in tex_slot.texture.color_ramp.elements: - ramp_positions.append(element.position) - ramp_colors_b.append(element.color[2]) - size_over_time_factor = tex_slot.size_factor - break + # Texture slots data + if psettings.texture_slots and len(psettings.texture_slots.items()) != 0: + for tex_slot in psettings.texture_slots: + if not tex_slot: break + if not tex_slot.use_map_size: break # TODO: check also for other influences + if tex_slot.texture and tex_slot.texture.use_color_ramp: + if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements: + ramp_el_len = len(tex_slot.texture.color_ramp.elements.items()) + for element in tex_slot.texture.color_ramp.elements: + ramp_positions.append(element.position) + ramp_colors_b.append(element.color[2]) + size_over_time_factor = tex_slot.size_factor + break # Outs @@ -72,6 +78,12 @@ def write(vert, particle_info=None, shadowmap=False): vert.add_uniform('float pd_random', '_particleRandom') vert.add_uniform('float pd_size', '_particleSize') + vert.add_const('float', 'P_NORMAL_FACTOR', str(normal_factor)) + + if (shadowmap and normal_factor != 0.0): + vert.add_out('vec3 N') + + if ramp_el_len != 0: vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor)) for i in range(ramp_el_len): @@ -143,6 +155,13 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('p_velocity.y += (fhash(gl_InstanceID + pd_random + pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];') vert.write('p_velocity.z += (fhash(gl_InstanceID + pd_random + 2 * pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];') + if normal_factor != 0.0: + vert.write('if (P_NORMAL_FACTOR != 0.0) {') + vert.write(' vec3 local_normal_dir = normalize(ipos);') + vert.write(' vec3 world_emitter_normal = normalize(N * local_normal_dir);') + vert.write(' p_velocity += world_emitter_normal * P_NORMAL_FACTOR;') + vert.write('}') + # factor_random = pd[1][3] # p.i = gl_InstanceID # particles.length = pd[0][3] @@ -262,6 +281,10 @@ def write(vert, particle_info=None, shadowmap=False): vert.add_out('float p_random') vert.write('p_random = fract(sin(gl_InstanceID) * 43758.5453);') + if out_size: + vert.add_out('float p_size') + vert.write('p_size = pd_size * (1 - (fhash(gl_InstanceID + 3 * pd[0][3] + pd_random) * pd_size_random));') + def write_tilesheet(vert): # tilesx, tilesy, framerate - pd[3][0], pd[3][1], pd[3][2] diff --git a/leenkx/blender/lnx/material/make_shader.py b/leenkx/blender/lnx/material/make_shader.py index 0a6e9414..b1db6cc7 100644 --- a/leenkx/blender/lnx/material/make_shader.py +++ b/leenkx/blender/lnx/material/make_shader.py @@ -328,6 +328,12 @@ def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[O if 'Scale' in inst: global_elems.append({'name': 'iscl', 'data': 'float3'}) + for attr in bo.lnx_instanced_attrs: + global_elems.append({'name': attr.name, 'data': f"float{attr.size}"}) + + if lnx.utils.export_morph_targets(bo): + global_elems.append({'name': 'imorph', 'data': 'float4'}) + elif inst == 'Off': # Ignore children of instanced objects, they are instanced even when set to 'Off' instancing_usage[1] = bo.parent is None or bo.parent.lnx_instanced == 'Off' diff --git a/leenkx/blender/lnx/material/node_meta.py b/leenkx/blender/lnx/material/node_meta.py index 7b5e88c4..7a85fe87 100644 --- a/leenkx/blender/lnx/material/node_meta.py +++ b/leenkx/blender/lnx/material/node_meta.py @@ -188,11 +188,14 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = { 'TEX_SKY': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_sky), 'TEX_VORONOI': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_voronoi), 'TEX_WAVE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_wave), + 'TEX_GABOR': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_gabor), + 'TEX_WHITE_NOISE': MaterialNodeMeta(parse_func=nodes_texture.parse_tex_white_noise), # --- nodes_vector 'BUMP': MaterialNodeMeta(parse_func=nodes_vector.parse_bump), 'CURVE_VEC': MaterialNodeMeta(parse_func=nodes_vector.parse_curvevec), 'DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_displacement), + 'VECTOR_DISPLACEMENT': MaterialNodeMeta(parse_func=nodes_vector.parse_vector_displacement), 'MAPPING': MaterialNodeMeta(parse_func=nodes_vector.parse_mapping), 'NORMAL': MaterialNodeMeta(parse_func=nodes_vector.parse_normal), 'NORMAL_MAP': MaterialNodeMeta(parse_func=nodes_vector.parse_normalmap), diff --git a/leenkx/blender/lnx/material/shader.py b/leenkx/blender/lnx/material/shader.py index 83625945..db8c656b 100644 --- a/leenkx/blender/lnx/material/shader.py +++ b/leenkx/blender/lnx/material/shader.py @@ -86,6 +86,10 @@ class ShaderContext: self.data['constants'] = [] self.constants = self.data['constants'] + if hasattr(self.material, 'lnx_solid'): + if self.material.lnx_solid: + self.constants.append({'floatValue': self.material.lnx_solid_factor, "is_lnx_parameter": True, 'link': '_mixColor', 'name': 'mixColor', 'type': 'float'}) + def add_elem(self, name, data): elem = { 'name': name, 'data': data } if elem not in self.data['vertex_elements']: @@ -95,6 +99,12 @@ class ShaderContext: def sort_vs(self): vs = [] ar = ['pos', 'nor', 'tex', 'tex1', 'morph', 'col', 'tang', 'bone', 'weight', 'ipos', 'irot', 'iscl'] + + if 'vertex_elements' in self.data: + for elem in self.data['vertex_elements']: + if elem['name'] not in ar: + ar.append(elem['name']) + for ename in ar: elem = self.get_elem(ename) if elem != None: @@ -399,7 +409,11 @@ class Shader: return vs = self.context.data['vertex_elements'] for e in vs: - self.add_in('vec' + self.data_size(e['data']) + ' ' + e['name']) + size = self.data_size(e['data']) + if size == '1': + self.add_in('float ' + e['name']) + else: + self.add_in('vec' + size + ' ' + e['name']) def validate(self): import re diff --git a/leenkx/blender/lnx/nodes_logic.py b/leenkx/blender/lnx/nodes_logic.py index a5917029..c624958d 100644 --- a/leenkx/blender/lnx/nodes_logic.py +++ b/leenkx/blender/lnx/nodes_logic.py @@ -122,11 +122,13 @@ class LNX_OT_AddNodeOverride(bpy.types.Operator): # https://github.com/blender/blender/blob/cf1e1ed46b7ec80edb0f43cb514d3601a1696ec1/source/blender/python/intern/bpy_rna.c#L2033-L2043 setting_dicts = [] for setting in self.settings.values(): - setting_dicts.append({ + item = { "name": setting.name, - "value": setting.value, - "array_index": setting.array_index - }) + "value": setting.value + } + if hasattr(setting, "array_index"): + item["array_index"] = setting.array_index + setting_dicts.append(item) bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform, settings=setting_dicts) return {'FINISHED'} diff --git a/leenkx/blender/lnx/props.py b/leenkx/blender/lnx/props.py index b00c4e29..c2ee0b2e 100644 --- a/leenkx/blender/lnx/props.py +++ b/leenkx/blender/lnx/props.py @@ -169,7 +169,7 @@ def init_properties(): # HTML5 Settings bpy.types.World.lnx_project_html5_copy = BoolProperty(name="Copy Files To Specified Folder", description="Copy files to the folder specified in the settings after publish", default=False, update=assets.invalidate_compiler_cache, set=set_project_html5_copy, get=get_project_html5_copy) bpy.types.World.lnx_project_html5_start_browser = BoolProperty(name="Run Browser After Copy", description="Run browser after copy", default=False, update=assets.invalidate_compiler_cache, set=set_project_html5_start_browser, get=get_project_html5_start_browser) - bpy.types.World.lnx_project_html5_popupmenu_in_browser = BoolProperty(name="Disable Browser Context Menu", description="Disable the browser context menu for the canvas element on the page", default=False, update=assets.invalidate_compiler_cache) + bpy.types.World.lnx_project_html5_popupmenu_in_browser = BoolProperty(name="Disable Browser Context Menu", description="Disable the browser context menu for the canvas element on the page", default=True, update=assets.invalidate_compiler_cache) # Windows Settings bpy.types.World.lnx_project_win_list_vs = EnumProperty( items=lnx.utils_vs.supported_versions, @@ -315,9 +315,10 @@ def init_properties(): ) bpy.types.World.lnx_copy_override = BoolProperty(name="Copy Override", description="Overrides any existing files when copying", default=False, update=assets.invalidate_compiled_data) bpy.types.World.lnx_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data) - bpy.types.World.lnx_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True) - bpy.types.World.lnx_no_traces = BoolProperty(name="No Traces", description="Don't compile trace calls in the program when publishing", default=False) - bpy.types.World.lnx_optimize_data = BoolProperty(name="Optimize Data", description="Export more efficient geometry and shader data when publishing, prolongs build times", default=True, update=assets.invalidate_compiled_data) + bpy.types.World.lnx_export_debug_json = BoolProperty(name="Export Debug JSON", description="Export Debug JSON to debug_json", default=False) + bpy.types.World.lnx_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output.", default=True) + bpy.types.World.lnx_no_traces = BoolProperty(name="No Traces", description="Don't compile trace calls in the program", default=False) + bpy.types.World.lnx_optimize_data = BoolProperty(name="Optimize Data", description="Export more efficient geometry and shader data when publishing, prolongs build times", default=False, update=assets.invalidate_compiled_data) bpy.types.World.lnx_deinterleaved_buffers = BoolProperty(name="Deinterleaved Buffers", description="Use deinterleaved vertex buffers", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_export_tangents = BoolProperty(name="Precompute Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data) bpy.types.World.lnx_batch_meshes = BoolProperty(name="Batch Meshes", description="Group meshes by materials to speed up rendering", default=False, update=assets.invalidate_compiler_cache) @@ -361,8 +362,8 @@ def init_properties(): name="Runtime", description="Runtime to use when launching the game", default='Krom', update=assets.invalidate_shader_cache) bpy.types.World.lnx_loadscreen = BoolProperty(name="Loading Screen", description="Show asset loading progress on published builds", default=True) bpy.types.World.lnx_vsync = BoolProperty(name="VSync", description="Vertical Synchronization", default=True, update=assets.invalidate_compiler_cache) - bpy.types.World.lnx_dce = BoolProperty(name="DCE", description="Enable dead code elimination for publish builds", default=True, update=assets.invalidate_compiler_cache) - bpy.types.World.lnx_asset_compression = BoolProperty(name="Asset Compression", description="Enable scene data compression with LZ4 when publishing. Warning: This will slow down export!", default=False, update=assets.invalidate_compiler_cache) + bpy.types.World.lnx_dce = BoolProperty(name="DCE", description="Enable dead code elimination", default=False, update=assets.invalidate_compiler_cache) + bpy.types.World.lnx_asset_compression = BoolProperty(name="Asset Compression", description="Enable scene data compression with LZ4. Warning: This will slow down export!", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_single_data_file = BoolProperty(name="Single Data File", description="Pack exported meshes and materials into single file", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_webview = BoolProperty(name="Enable Webview", description="Create a default embedded webview for DOM interaction", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_write_config = BoolProperty(name="Write Config", description="Allow this project to be configured at runtime via a JSON file", default=False, update=assets.invalidate_compiler_cache) @@ -391,6 +392,14 @@ def init_properties(): description='Whether to use instancing to draw the children of this object. If enabled, this option defines what attributes may vary between the instances', update=assets.invalidate_instance_cache, override={'LIBRARY_OVERRIDABLE'}) + bpy.types.Object.lnx_instanced_rot_type = EnumProperty( + items = [('Local', 'Local', 'Rotate around instance origin'), + ('Global', 'Global', 'Rotate around world origin')], + name="Rotation Type", + default='Local', + description='Rotation space for instanced children', + update=assets.invalidate_compiler_cache) + bpy.types.Material.lnx_instanced_rot_type = bpy.props.StringProperty(default='Local') bpy.types.Object.lnx_export = compatible_prop(BoolProperty, name="Export", description="Export object data", default=True, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'}) @@ -398,6 +407,11 @@ def init_properties(): bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04) + bpy.types.Object.lnx_soft_body_linear_stiffness = FloatProperty(name="Soft Body Linear Stiffness", description="Linear Stiffness", default=0.9) + bpy.types.Object.lnx_soft_body_friction = FloatProperty(name="Soft Body Friction", description="Friction", default=0.5) + bpy.types.Object.lnx_soft_body_damping = FloatProperty(name="Soft Body Damping", description="Damping", default=0.01) + bpy.types.Object.lnx_soft_body_angular_stiffness = FloatProperty(name="Soft Body Angular Stiffness", description="Angular Stiffness", default=0.9) + bpy.types.Object.lnx_soft_body_pressure = FloatProperty(name="Soft Body Pressure", description="Pressure", default=0.0) bpy.types.Object.lnx_rb_linear_factor = FloatVectorProperty(name="Linear Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1]) bpy.types.Object.lnx_rb_angular_factor = FloatVectorProperty(name="Angular Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1]) bpy.types.Object.lnx_rb_angular_friction = FloatProperty(name="Angular Friction", description="Angular Friction", default=0.1) @@ -441,6 +455,9 @@ def init_properties(): bpy.types.Mesh.lnx_cached = BoolProperty(name="Mesh Cached", description="No need to reexport mesh data", default=False) bpy.types.Mesh.lnx_aabb = FloatVectorProperty(name="AABB", size=3, default=[0,0,0]) bpy.types.Mesh.lnx_dynamic_usage = BoolProperty(name="Dynamic Usage", description="Mesh data can change at runtime", default=False) + + bpy.types.Curve.lnx_draw_strength = FloatProperty(name="Draw Strength", default=0.005) + bpy.types.Curve.lnx_draw_color = FloatVectorProperty(name="Draw Color", size=4, default=[0,0,0,1], subtype='COLOR', min=0, max=1) bpy.types.Curve.lnx_cached = BoolProperty(name="Mesh Cached", description="No need to reexport curve data", default=False) bpy.types.Curve.lnx_aabb = FloatVectorProperty(name="AABB", size=3, default=[0,0,0]) bpy.types.Curve.lnx_dynamic_usage = BoolProperty(name="Dynamic Data Usage", description="Curve data can change at runtime", default=False) @@ -483,6 +500,8 @@ def init_properties(): bpy.types.Material.lnx_depth_read = BoolProperty(name="Read Depth", description="Allow this material to read from a depth texture which is copied from the depth buffer. The meshes using this material will be drawn after all meshes that don't read from the depth texture", default=False) bpy.types.Material.lnx_overlay = BoolProperty(name="Overlay", description="Renders the material, unshaded, over other shaded materials", default=False) bpy.types.Material.lnx_decal = BoolProperty(name="Decal", default=False) + bpy.types.Material.lnx_solid = BoolProperty(name="Solid", default=False, description="Renders material as RGB Color (Only Forward)") + bpy.types.Material.lnx_solid_factor = FloatProperty(name="Factor", default=0.0, description="mix RGB Color and Shader, values in range [0, 1]. Can be accessed with shader uniform _mixColor.") bpy.types.Material.lnx_compare_mode = EnumProperty( items=[ ('always', 'Always', 'Always'), @@ -670,7 +689,7 @@ def init_properties(): bpy.types.World.lnx_action_retarget_rot_y = BoolProperty(name="Rotation Y", description="Retarget Rotation Y", default=False) bpy.types.World.lnx_action_retarget_rot_z = BoolProperty(name="Rotation Z", description="Retarget Rotation Z", default=False) bpy.types.World.lnx_action_retarget_rot_z = BoolProperty(name="Rotation Z", description="Retarget Rotation Z", default=True) - bpy.types.World.lnx_retarget_lnxature = PointerProperty(name="Armature", description="Armature", type=bpy.types.Object) + bpy.types.World.lnx_retarget_armature = PointerProperty(name="Armature", description="Armature", type=bpy.types.Object) bpy.types.World.lnx_retarget_overwrite = BoolProperty(name="Overwrite", description="Overwrite action", default=True) bpy.types.World.lnx_retarget_from = StringProperty(name="From Bone", description="From Bone") bpy.types.World.lnx_retarget_to = StringProperty(name="To Bone", description="To Bone") diff --git a/leenkx/blender/lnx/props_action.py b/leenkx/blender/lnx/props_action.py index 6cc039d9..1e1e14f4 100644 --- a/leenkx/blender/lnx/props_action.py +++ b/leenkx/blender/lnx/props_action.py @@ -151,22 +151,22 @@ class LnxDrawRetargetPanel: layout.label(text='Action: ' + action.name) wrd = bpy.data.worlds['Lnx'] # Armature Object - layout.prop(wrd, 'lnx_retarget_lnxature') + layout.prop(wrd, 'lnx_retarget_armature') con = False # From, To bones - if wrd.lnx_retarget_lnxature: - if wrd.lnx_retarget_lnxature.type == 'ARMATURE': + if wrd.lnx_retarget_armature: + if wrd.lnx_retarget_armature.type == 'ARMATURE': con = True sub = layout.row(align=True) sub.label(text="From Bone:") sub.prop_search(wrd, 'lnx_retarget_from', - wrd.lnx_retarget_lnxature.data, "bones", text="") - con = cls.check_bone(wrd.lnx_retarget_lnxature.data, wrd.lnx_retarget_from) + wrd.lnx_retarget_armature.data, "bones", text="") + con = cls.check_bone(wrd.lnx_retarget_armature.data, wrd.lnx_retarget_from) sub = layout.row(align=True) sub.label(text="To Bone:") sub.prop_search(wrd,'lnx_retarget_to', - wrd.lnx_retarget_lnxature.data, "bones", text="") - con = cls.check_bone(wrd.lnx_retarget_lnxature.data, wrd.lnx_retarget_to) + wrd.lnx_retarget_armature.data, "bones", text="") + con = cls.check_bone(wrd.lnx_retarget_armature.data, wrd.lnx_retarget_to) # Check if bones exist condition = con and bool(wrd.lnx_retarget_from) and bool(wrd.lnx_retarget_to) section = layout.column() diff --git a/leenkx/blender/lnx/props_properties.py b/leenkx/blender/lnx/props_properties.py index 2351b78c..765796cc 100644 --- a/leenkx/blender/lnx/props_properties.py +++ b/leenkx/blender/lnx/props_properties.py @@ -18,7 +18,7 @@ class LnxArrayItem(bpy.types.PropertyGroup): integer_prop: IntProperty(name="Integer", default=0) float_prop: FloatProperty(name="Float", default=0.0) boolean_prop: BoolProperty(name="Boolean", default=False) - + vector_prop: FloatVectorProperty(name="Vector", size=3, default=[0.0, 0.0, 0.0]) class LnxPropertyListItem(bpy.types.PropertyGroup): type_prop: EnumProperty( @@ -26,6 +26,7 @@ class LnxPropertyListItem(bpy.types.PropertyGroup): ('integer', 'Integer', 'Integer'), ('float', 'Float', 'Float'), ('boolean', 'Boolean', 'Boolean'), + ('vector', 'Vector', 'Vector'), ('array', 'Array', 'Array'), ], name = "Type") @@ -34,6 +35,7 @@ class LnxPropertyListItem(bpy.types.PropertyGroup): integer_prop: IntProperty(name="Integer", description="A name for this item", default=0) float_prop: FloatProperty(name="Float", description="A name for this item", default=0.0) boolean_prop: BoolProperty(name="Boolean", description="A name for this item", default=False) + vector_prop: FloatVectorProperty(name="Vector", size=3, default=[0.0, 0.0, 0.0]) array_prop: CollectionProperty(type=LnxArrayItem) array_item_type: EnumProperty( items = [ @@ -41,7 +43,7 @@ class LnxPropertyListItem(bpy.types.PropertyGroup): ('integer', 'Integer', 'Integer'), ('float', 'Float', 'Float'), ('boolean', 'Boolean', 'Boolean'), - # Add more types here as needed + ('vector', 'Vector', 'Vector'), ], name = "New Item Type", default = 'string' @@ -65,20 +67,26 @@ class LNX_UL_ArrayItemList(bpy.types.UIList): layout.prop(item, "float_prop", text="") elif array_type == 'boolean': layout.prop(item, "boolean_prop", text="") - # Add other types as needed + elif array_type == 'vector': + layout.prop(item, "vector_prop", text="") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="", icon="OBJECT_DATAMODE") +def get_lnx_target(context): + if context.object is not None and context.object.mode == 'OBJECT': + return context.object + return context.scene + class LnxArrayAddItem(bpy.types.Operator): bl_idname = "lnx_array.add_item" bl_label = "Add Array Item" def execute(self, context): - obj = bpy.context.object - if obj.lnx_propertylist and obj.lnx_propertylist_index < len(obj.lnx_propertylist): - selected_item = obj.lnx_propertylist[obj.lnx_propertylist_index] + target = get_lnx_target(context) + if target.lnx_propertylist and target.lnx_propertylist_index < len(target.lnx_propertylist): + selected_item = target.lnx_propertylist[target.lnx_propertylist_index] # Create a new item in the array new_item = selected_item.array_prop.add() @@ -86,13 +94,15 @@ class LnxArrayAddItem(bpy.types.Operator): # Set the type of the new item based on the selected type new_item_type = selected_item.array_item_type if new_item_type == 'string': - new_item.string_prop = "" # Default value for string + new_item.string_prop = "" elif new_item_type == 'integer': - new_item.integer_prop = 0 # Default value for integer + new_item.integer_prop = 0 elif new_item_type == 'float': - new_item.float_prop = 0.0 # Default value for float + new_item.float_prop = 0.0 elif new_item_type == 'boolean': - new_item.boolean_prop = False # Default value for boolean + new_item.boolean_prop = False + elif new_item_type == 'vector': + new_item.vector_prop = [0.0, 0.0, 0.0] # Set the index of the new item new_item_index = len(selected_item.array_prop) - 1 @@ -109,9 +119,9 @@ class LnxArrayRemoveItem(bpy.types.Operator): bl_label = "Remove Array Item" def execute(self, context): - obj = bpy.context.object - if obj.lnx_propertylist and obj.lnx_propertylist_index < len(obj.lnx_propertylist): - selected_item = obj.lnx_propertylist[obj.lnx_propertylist_index] + target = get_lnx_target(context) + if target.lnx_propertylist and target.lnx_propertylist_index < len(target.lnx_propertylist): + selected_item = target.lnx_propertylist[target.lnx_propertylist_index] if selected_item.array_prop: selected_item.array_prop.remove(selected_item.array_index) if selected_item.array_index > 0: @@ -130,7 +140,6 @@ class LNX_UL_PropertyList(bpy.types.UIList): layout.label(text="", icon="OBJECT_DATAMODE") class LnxPropertyListNewItem(bpy.types.Operator): - # Add a new item to the list bl_idname = "lnx_propertylist.new_item" bl_label = "New" @@ -139,6 +148,7 @@ class LnxPropertyListNewItem(bpy.types.Operator): ('integer', 'Integer', 'Integer'), ('float', 'Float', 'Float'), ('boolean', 'Boolean', 'Boolean'), + ('vector', 'Vector', 'Vector'), ('array', 'Array', 'Array'), ], @@ -153,138 +163,101 @@ class LnxPropertyListNewItem(bpy.types.Operator): layout.prop(self, "type_prop", expand=True) def execute(self, context): - obj = bpy.context.object - prop = obj.lnx_propertylist.add() + target = get_lnx_target(context) + prop = target.lnx_propertylist.add() prop.type_prop = self.type_prop - obj.lnx_propertylist_index = len(obj.lnx_propertylist) - 1 + target.lnx_propertylist_index = len(target.lnx_propertylist) - 1 return{'FINISHED'} class LnxPropertyListDeleteItem(bpy.types.Operator): - # Delete the selected item from the list bl_idname = "lnx_propertylist.delete_item" bl_label = "Deletes an item" @classmethod def poll(self, context): - """ Enable if there's something in the list """ - obj = bpy.context.object - if obj == None: - return False - return len(obj.lnx_propertylist) > 0 + target = get_lnx_target(context) + return len(target.lnx_propertylist) > 0 def execute(self, context): - obj = bpy.context.object - lst = obj.lnx_propertylist - index = obj.lnx_propertylist_index - - if len(lst) <= index: - return{'FINISHED'} - - lst.remove(index) - - if index > 0: - index = index - 1 - - obj.lnx_propertylist_index = index - return{'FINISHED'} + target = get_lnx_target(context) + target.lnx_propertylist.remove(target.lnx_propertylist_index) + target.lnx_propertylist_index = max(0, target.lnx_propertylist_index - 1) + return {'FINISHED'} class LnxPropertyListMoveItem(bpy.types.Operator): - # Move an item in the list bl_idname = "lnx_propertylist.move_item" - bl_label = "Move an item in the list" - direction: EnumProperty( - items=( - ('UP', 'Up', ""), - ('DOWN', 'Down', ""),)) - - def move_index(self): - obj = bpy.context.object - index = obj.lnx_propertylist_index - list_length = len(obj.lnx_propertylist) - 1 - new_index = 0 - - if self.direction == 'UP': - new_index = index - 1 - elif self.direction == 'DOWN': - new_index = index + 1 - - new_index = max(0, min(new_index, list_length)) - obj.lnx_propertylist.move(index, new_index) - obj.lnx_propertylist_index = new_index - + bl_label = "Move" + direction: EnumProperty(items=(('UP', 'Up', ""), ('DOWN', 'Down', ""))) def execute(self, context): - obj = bpy.context.object - list = obj.lnx_propertylist - index = obj.lnx_propertylist_index - - if self.direction == 'DOWN': - neighbor = index + 1 - self.move_index() - - elif self.direction == 'UP': - neighbor = index - 1 - self.move_index() - else: - return{'CANCELLED'} - return{'FINISHED'} + target = get_lnx_target(context) + idx = target.lnx_propertylist_index + new_idx = idx - 1 if self.direction == 'UP' else idx + 1 + if 0 <= new_idx < len(target.lnx_propertylist): + target.lnx_propertylist.move(idx, new_idx) + target.lnx_propertylist_index = new_idx + return {'FINISHED'} def draw_properties(layout, obj): - layout.label(text="Properties") - - # Draw the LNX_UL_PropertyList - rows = 4 if len(obj.lnx_propertylist) > 1 else 2 - row = layout.row() - row.template_list("LNX_UL_PropertyList", "The_List", obj, "lnx_propertylist", obj, "lnx_propertylist_index", rows=rows) - - # Column for buttons next to LNX_UL_PropertyList - col = row.column(align=True) - col.operator("lnx_propertylist.new_item", icon='ADD', text="") - col.operator("lnx_propertylist.delete_item", icon='REMOVE', text="") + layout.use_property_split = False + main_col = layout.column() + main_col.label(text="Properties") + row = main_col.row() + row.template_list("LNX_UL_PropertyList", "", obj, "lnx_propertylist", obj, "lnx_propertylist_index", rows=4) + btn_col = row.column(align=True) + btn_col.operator("lnx_propertylist.new_item", icon='ADD', text="") + btn_col.operator("lnx_propertylist.delete_item", icon='REMOVE', text="") if len(obj.lnx_propertylist) > 1: - col.separator() - col.operator("lnx_propertylist.move_item", icon='TRIA_UP', text="").direction = 'UP' - col.operator("lnx_propertylist.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN' - - # Draw UI List for array items if the selected property is an array - if obj.lnx_propertylist and obj.lnx_propertylist_index < len(obj.lnx_propertylist): - selected_item = obj.lnx_propertylist[obj.lnx_propertylist_index] - if selected_item.type_prop == 'array': - layout.label(text="Array Items") - - # Dropdown to select the type of new array items - layout.prop(selected_item, "array_item_type", text="Array Type") - - # Template list for array items - row = layout.row() - row.template_list("LNX_UL_ArrayItemList", "", selected_item, "array_prop", selected_item, "array_index", rows=rows) - - # Column for buttons next to the array items list - col = row.column(align=True) - col.operator("lnx_array.add_item", icon='ADD', text="") - col.operator("lnx_array.remove_item", icon='REMOVE', text="") - - - + btn_col.separator() + btn_col.operator("lnx_propertylist.move_item", icon='TRIA_UP', text="").direction = 'UP' + btn_col.operator("lnx_propertylist.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN' + + if len(obj.lnx_propertylist) > 0 and obj.lnx_propertylist_index < len(obj.lnx_propertylist): + item = obj.lnx_propertylist[obj.lnx_propertylist_index] + if item.type_prop == 'array': + main_col.separator() + main_col.label(text="Array Items") + main_col.prop(item, "array_item_type", text="Type") + row_arr = main_col.row() + row_arr.template_list("LNX_UL_ArrayItemList", "", item, "array_prop", item, "array_index", rows=4) + btn_arr = row_arr.column(align=True) + btn_arr.operator("lnx_array.add_item", icon='ADD', text="") + btn_arr.operator("lnx_array.remove_item", icon='REMOVE', text="") +class LNX_PT_ObjectPropsPanel(bpy.types.Panel): + bl_label = "Leenkx Props" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + def draw(self, context): + draw_properties(self.layout, context.object) +class LNX_PT_ScenePropsPanel(bpy.types.Panel): + bl_label = "Leenkx Props" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + def draw(self, context): + draw_properties(self.layout, context.scene) __REG_CLASSES = ( - LnxArrayItem, - LnxPropertyListItem, - LNX_UL_PropertyList, - LNX_UL_ArrayItemList, - LnxPropertyListNewItem, - LnxPropertyListDeleteItem, - LnxPropertyListMoveItem, - LnxArrayAddItem, - LnxArrayRemoveItem, + LnxArrayItem, LnxPropertyListItem, LNX_UL_PropertyList, LNX_UL_ArrayItemList, + LnxPropertyListNewItem, LnxPropertyListDeleteItem, LnxPropertyListMoveItem, + LnxArrayAddItem, LnxArrayRemoveItem, LNX_PT_ObjectPropsPanel, LNX_PT_ScenePropsPanel, ) -__reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES) def register(): - __reg_classes() + for cls in __REG_CLASSES: + bpy.utils.register_class(cls) bpy.types.Object.lnx_propertylist = CollectionProperty(type=LnxPropertyListItem) - bpy.types.Object.lnx_propertylist_index = IntProperty(name="Index for lnx_propertylist", default=0) - # New property for tracking the active index in array items - bpy.types.PropertyGroup.array_index = IntProperty(name="Array Index", default=0) + bpy.types.Object.lnx_propertylist_index = IntProperty(name="Index", default=0) + bpy.types.Scene.lnx_propertylist = CollectionProperty(type=LnxPropertyListItem) + bpy.types.Scene.lnx_propertylist_index = IntProperty(name="Index", default=0) + +def unregister(): + for cls in reversed(__REG_CLASSES): + bpy.utils.unregister_class(cls) + del bpy.types.Object.lnx_propertylist + del bpy.types.Object.lnx_propertylist_index + del bpy.types.Scene.lnx_propertylist + del bpy.types.Scene.lnx_propertylist_index diff --git a/leenkx/blender/lnx/props_renderpath.py b/leenkx/blender/lnx/props_renderpath.py index ed10debf..da3ed14c 100644 --- a/leenkx/blender/lnx/props_renderpath.py +++ b/leenkx/blender/lnx/props_renderpath.py @@ -327,6 +327,7 @@ class LnxRPListItem(bpy.types.PropertyGroup): name="Map Size", description="Prefiltered map size", default='1024', update=assets.invalidate_envmap_data) rp_autoexposure: BoolProperty(name="Auto Exposure", description="Adjust exposure based on luminance", default=False, update=update_renderpath) rp_compositornodes: BoolProperty(name="Compositor", description="Draw compositor nodes", default=True, update=update_renderpath) + lnx_custom_compositor: BoolProperty(name="Custom Compositor", description="Enable custom compositor pass in project folder", default=False, update=update_renderpath) rp_shadows: BoolProperty(name="Shadows", description="Enable shadow casting", default=True, update=update_renderpath) rp_max_lights: EnumProperty( items=[('4', '4', '4'), @@ -468,7 +469,7 @@ class LnxRPListItem(bpy.types.PropertyGroup): name='Depth Texture', description='Whether materials can read from a depth texture', default='Auto', update=update_depth_texture_state) rp_stereo: BoolProperty(name="VR", description="Stereo rendering", default=False, update=update_renderpath) rp_water: BoolProperty(name="Water", description="Enable water surface pass", default=False, update=update_renderpath) - rp_pp: BoolProperty(name="Realtime post process", description="Realtime post process", default=False, update=update_renderpath) + rp_pp: BoolProperty(name="Realtime Post Process", description="Realtime Post Process", default=False, update=update_renderpath) lnx_clouds: BoolProperty(name="Clouds", description="Enable clouds pass", default=False, update=assets.invalidate_shader_cache) lnx_ssrs: BoolProperty(name="SSRS", description="Screen-space ray-traced shadows", default=False, update=assets.invalidate_shader_cache) lnx_micro_shadowing: BoolProperty(name="Micro Shadowing", description="Use the shaders' occlusion parameter to compute micro shadowing for the scene's sun lamp. This option is not available for render paths using mobile or solid material models", default=False, update=assets.invalidate_shader_cache) @@ -657,6 +658,7 @@ class LnxRPListItem(bpy.types.PropertyGroup): max=1000.0 ) lnx_fisheye: BoolProperty(name="Fish Eye", default=False, update=assets.invalidate_shader_cache) + lnx_fisheye_strength: FloatProperty(name="Strength", default=0.01, update=assets.invalidate_shader_cache) lnx_vignette: BoolProperty(name="Vignette", default=False, update=assets.invalidate_shader_cache) lnx_vignette_strength: FloatProperty(name="Strength", default=0.7, update=assets.invalidate_shader_cache) lnx_lensflare: BoolProperty(name="Lens Flare", default=False, update=assets.invalidate_shader_cache) diff --git a/leenkx/blender/lnx/props_tilesheet.py b/leenkx/blender/lnx/props_tilesheet.py index 9b014ec4..9edee740 100644 --- a/leenkx/blender/lnx/props_tilesheet.py +++ b/leenkx/blender/lnx/props_tilesheet.py @@ -292,7 +292,7 @@ class LNX_PT_TilesheetPanel(bpy.types.Panel): bl_label = "Leenkx Tilesheet" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" - bl_context = "object" + bl_context = "render" bl_options = {'DEFAULT_CLOSED'} @classmethod diff --git a/leenkx/blender/lnx/props_traits.py b/leenkx/blender/lnx/props_traits.py index 6a7afd44..bb26b2f9 100644 --- a/leenkx/blender/lnx/props_traits.py +++ b/leenkx/blender/lnx/props_traits.py @@ -143,7 +143,10 @@ class LNX_UL_TraitList(bpy.types.UIList): row.prop(item, "enabled_prop") # Display " " for props without a name to right-align the # fake_user button - row.label(text=item.name if item.name != "" else " ", icon=custom_icon, icon_value=custom_icon_value) + label_text = item.name + if item.type_prop == "Logic Nodes" and item.node_tree_prop is not None: + label_text = item.node_tree_prop.name + row.label(text=label_text if label_text != "" else " ", icon=custom_icon, icon_value=custom_icon_value) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' diff --git a/leenkx/blender/lnx/props_ui.py b/leenkx/blender/lnx/props_ui.py index 789ecde3..cedb3b0d 100644 --- a/leenkx/blender/lnx/props_ui.py +++ b/leenkx/blender/lnx/props_ui.py @@ -64,6 +64,25 @@ if lnx.is_reload(__name__): else: lnx.enable_reload(__name__) +class LnxInstancedAttr(bpy.types.PropertyGroup): + name: bpy.props.StringProperty() + size: bpy.props.IntProperty(min=1, max=4, default=1) + +class LNX_OT_AddInstancedAttr(bpy.types.Operator): + bl_idname = "lnx.add_instanced_attr" + bl_label = "" + def execute(self, context): + context.object.lnx_instanced_attrs.add() + return {'FINISHED'} + +class LNX_OT_DelInstancedAttr(bpy.types.Operator): + bl_idname = "lnx.del_instanced_attr" + bl_label = "" + idx: bpy.props.IntProperty() + def execute(self, context): + context.object.lnx_instanced_attrs.remove(self.idx) + return {'FINISHED'} + class LNX_PT_ObjectPropsPanel(bpy.types.Panel): """Menu in object region.""" bl_label = "Leenkx Props" @@ -88,10 +107,25 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel): col.prop(obj, 'lnx_spawn') col.prop(obj, 'lnx_mobile') col.prop(obj, 'lnx_animation_enabled') - col.prop(obj, 'lnx_visible_shadow') + sub = col.column() + sub.enabled = False + sub.prop(obj, 'lnx_visible_shadow') if obj.type == 'MESH': layout.prop(obj, 'lnx_instanced') + if 'Rot' in obj.lnx_instanced: + layout.prop(obj, 'lnx_instanced_rot_type') + + if obj.lnx_instanced != 'Off': + box = layout.box() + box.label(text="Instanced Attributes (must start with i)") + for i, item in enumerate(obj.lnx_instanced_attrs): + row = box.row() + row.prop(item, "name", text="") + row.prop(item, "size", text="") + op = row.operator("lnx.del_instanced_attr", icon='X', text="") + op.idx = i + box.operator("lnx.add_instanced_attr", icon='ADD') # Properties list lnx.props_properties.draw_properties(layout, obj) @@ -285,8 +319,16 @@ class LNX_PT_PhysicsPropsPanel(bpy.types.Panel): layout.prop(obj, 'lnx_rb_ccd') layout.prop(obj, 'lnx_rb_interpolate') - if obj.soft_body is not None: + is_cloth = any(m.type == 'CLOTH' for m in obj.modifiers) + + if obj.soft_body is not None or is_cloth: layout.prop(obj, 'lnx_soft_body_margin') + if is_cloth: + layout.prop(obj, 'lnx_soft_body_friction') + layout.prop(obj, 'lnx_soft_body_damping') + layout.prop(obj, 'lnx_soft_body_linear_stiffness') + layout.prop(obj, 'lnx_soft_body_angular_stiffness') + layout.prop(obj, 'lnx_soft_body_pressure') if obj.rigid_body_constraint is not None: layout.prop(obj, 'lnx_relative_physics_constraint') @@ -342,6 +384,9 @@ class LNX_PT_DataPropsPanel(bpy.types.Panel): elif obj.type == 'MESH' or obj.type == 'FONT' or obj.type == 'META': layout.prop(obj.data, 'lnx_dynamic_usage') layout.operator("lnx.invalidate_cache") + elif obj.type == 'CURVE': + layout.prop(obj.data, 'lnx_draw_strength') + layout.prop(obj.data, 'lnx_draw_color') elif obj.type == 'LIGHT': layout.prop(obj.data, 'lnx_clip_start') layout.prop(obj.data, 'lnx_clip_end') @@ -405,6 +450,8 @@ class LNX_PT_ScenePropsPanel(bpy.types.Panel): row = layout.row() column = row.column() row.prop(scene, 'lnx_export') + + lnx.props_properties.draw_properties(layout, scene) class InvalidateCacheButton(bpy.types.Operator): """Delete cached mesh data""" @@ -711,6 +758,12 @@ class LNX_PT_MaterialPropsPanel(bpy.types.Panel): layout.prop(mat, 'lnx_depth_read') layout.prop(mat, 'lnx_overlay') layout.prop(mat, 'lnx_decal') + columnb = layout.column() + columnb.enabled = lnx.utils.get_rp().rp_renderer == 'Forward' + columnb.prop(mat, 'lnx_solid') + columnc = columnb.column() + columnc.enabled = mat.lnx_solid + columnc.prop(mat, 'lnx_solid_factor') layout.prop(mat, 'lnx_discard') columnb = layout.column() columnb.enabled = mat.lnx_discard @@ -958,8 +1011,14 @@ class LNX_PT_LeenkxExporterPanel(bpy.types.Panel): col.prop(wrd, 'lnx_no_traces') col = column_with_heading(layout, 'Data', align=True) - col.prop(wrd, 'lnx_minimize') - col.prop(wrd, 'lnx_optimize_data') + sub = col.column() + sub.enabled = False + sub.prop(wrd, 'lnx_minimize') + + col.prop(wrd, 'lnx_export_debug_json') + sub = col.column() + sub.enabled = False + sub.prop(wrd, 'lnx_optimize_data') col.prop(wrd, 'lnx_asset_compression') col.prop(wrd, 'lnx_single_data_file') @@ -1807,7 +1866,7 @@ class LNX_PT_RenderPathShadowsPanel(bpy.types.Panel): col = colatlas_single.row() col.alignment = 'RIGHT' - col.label(text=f'Enough space for { point_lights } point lights or { spot_lights } spot lights or { dir_lights } directional lights.') + col.label(text=f'Enough space for { point_lights } point lights or { spot_lights } spot lights or { dir_lights } directional lights (limit 1 sun light per scene).') else: # show size for all types colatlas_mixed = colatlas.column() @@ -2090,6 +2149,43 @@ class LNX_PT_RenderPathPostProcessPanel(bpy.types.Panel): if rpdat.lnx_chromatic_aberration_type == "Spectral": col.prop(rpdat, 'lnx_chromatic_aberration_samples') +class LNX_OT_EditCustomCompositor(bpy.types.Operator): + bl_idname = 'lnx.edit_custom_compositor' + bl_label = 'Edit' + bl_options = {'INTERNAL'} + + file_name: bpy.props.StringProperty() + + def execute(self, context): + import os + target_file = os.path.join(lnx.utils.get_fp(), 'Compositor', self.file_name) + lnx.utils.open_editor(target_file) + return {'FINISHED'} + +def init_custom_compositor(): + import os + import shutil + + fp = lnx.utils.get_fp() + if fp == "": + return + + custom_dir = os.path.join(fp, 'Compositor') + if not os.path.exists(custom_dir): + os.makedirs(custom_dir) + + sdk_path = lnx.utils.get_sdk_path() + source_dir = os.path.join(sdk_path, 'leenkx', 'Shaders', 'compositor_pass') + + files = ['compositor_pass.json', 'compositor_pass.frag.glsl', 'compositor_pass.vert.glsl'] + + for f in files: + target = os.path.join(custom_dir, f) + if not os.path.exists(target): + source = os.path.join(source_dir, f) + if os.path.exists(source): + shutil.copy(source, target) + class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel): bl_label = "Compositor" bl_space_type = "PROPERTIES" @@ -2115,6 +2211,18 @@ class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel): rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index] layout.enabled = rpdat.rp_compositornodes + + if rpdat.lnx_custom_compositor: + init_custom_compositor() + + row_custom = layout.row(align=True) + row_custom.prop(rpdat, 'lnx_custom_compositor', text="Custom") + sub = row_custom.row(align=True) + sub.enabled = rpdat.lnx_custom_compositor + sub.operator("lnx.edit_custom_compositor", icon='EDITMODE_HLT', text="Frag").file_name = "compositor_pass.frag.glsl" + sub.operator("lnx.edit_custom_compositor", icon='FILE_TEXT', text="Json").file_name = "compositor_pass.json" + layout.separator() + layout.prop(rpdat, 'lnx_tonemap') layout.prop(rpdat, 'lnx_dithering') if rpdat.lnx_dithering != 'Off': @@ -2164,7 +2272,7 @@ class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel): layout.separator() col = layout.column() - col.prop(rpdat, 'lnx_fisheye') + draw_conditional_prop(col, 'Fish Eye', rpdat, 'lnx_fisheye', 'lnx_fisheye_strength') col.prop(rpdat, 'lnx_lensflare') layout.separator() @@ -2461,10 +2569,8 @@ class LnxPrintTraitsButton(bpy.types.Operator): for s in bpy.data.scenes: print('Scene: {0}'.format(s.name)) for t in s.lnx_traitlist: - if not t.enabled_prop: - continue tname = "undefined" - if t.type_prop == 'Haxe Script' or "Bundled": + if t.type_prop in ('Haxe Script', 'Bundled Script'): tname = t.class_name_prop if t.type_prop == 'Logic Nodes': tname = t.node_tree_prop.name @@ -2472,13 +2578,13 @@ class LnxPrintTraitsButton(bpy.types.Operator): tname = t.canvas_name_prop if t.type_prop == 'WebAssembly': tname = t.webassembly_prop - print('Scene Trait: {0} ("{1}")'.format(s.name, tname)) + status = "" if t.enabled_prop else "[D]" + fake_user = "[FU]" if t.fake_user else "" + print(' Scene Trait: {0} ("{1}") {2} {3}'.format(s.name, tname, status, fake_user)) for o in s.objects: for t in o.lnx_traitlist: - if not t.enabled_prop: - continue tname = "undefined" - if t.type_prop == 'Haxe Script' or "Bundled": + if t.type_prop in ('Haxe Script', 'Bundled Script'): tname = t.class_name_prop if t.type_prop == 'Logic Nodes': tname = t.node_tree_prop.name @@ -2486,7 +2592,9 @@ class LnxPrintTraitsButton(bpy.types.Operator): tname = t.canvas_name_prop if t.type_prop == 'WebAssembly': tname = t.webassembly_prop - print(' Object Trait: {0} ("{1}")'.format(o.name, tname)) + status = "" if t.enabled_prop else "[D]" + fake_user = "[FU]" if t.fake_user else "" + print(' Object Trait: {0} ("{1}") {2} {3}'.format(o.name, tname, status, fake_user)) return{'FINISHED'} class LNX_PT_MaterialNodePanel(bpy.types.Panel): @@ -2961,6 +3069,9 @@ def draw_multiline_with_icon(layout: bpy.types.UILayout, layout_width_px: int, i __REG_CLASSES = ( + LnxInstancedAttr, + LNX_OT_AddInstancedAttr, + LNX_OT_DelInstancedAttr, LNX_PT_ObjectPropsPanel, LNX_PT_ModifiersPropsPanel, LNX_PT_ParticlesPropsPanel, @@ -3002,6 +3113,7 @@ __REG_CLASSES = ( LNX_PT_RenderPathWorldPanel, LNX_PT_RenderPathPostProcessPanel, LNX_PT_RenderPathCompositorPanel, + LNX_OT_EditCustomCompositor, LNX_PT_BakePanel, # LnxVirtualInputPanel, LeenkxPlayButton, @@ -3051,6 +3163,8 @@ def register(): bpy.types.Material.lnx_bind_textures_list = CollectionProperty(type=LNX_PG_BindTexturesListItem) bpy.types.Material.lnx_bind_textures_list_index = IntProperty(name='Index for lnx_bind_textures_list', default=0) + bpy.types.Object.lnx_instanced_attrs = bpy.props.CollectionProperty(type=LnxInstancedAttr) + def unregister(): bpy.types.NODE_MT_context_menu.remove(draw_custom_node_menu) diff --git a/leenkx/blender/lnx/utils.py b/leenkx/blender/lnx/utils.py index c204cd02..c6c74f77 100644 --- a/leenkx/blender/lnx/utils.py +++ b/leenkx/blender/lnx/utils.py @@ -12,6 +12,7 @@ import shutil import subprocess from typing import Any, Dict, Iterable, List, Optional, Tuple, Union import webbrowser +import unicodedata import numpy as np @@ -108,7 +109,23 @@ def write_lnx(filepath, output): else: filepath_json = filepath.split('.lnx')[0] + '.json' with open(filepath_json, 'w') as f: - f.write(json.dumps(output, sort_keys=True, indent=4, cls=NumpyEncoder)) + f.write(json.dumps(output, sort_keys=True, ensure_ascii=False, indent=4, cls=NumpyEncoder)) + + if getattr(bpy.data.worlds['Lnx'], 'lnx_export_debug_json', False): + try: + base_dir = os.path.dirname(filepath) + debug_dir = os.path.join(base_dir, 'debug_json') + + if not os.path.exists(debug_dir): + os.makedirs(debug_dir) + + clean_name = os.path.basename(filepath).replace('.lnx', '').replace('.lz4', '') + debug_path = os.path.join(debug_dir, clean_name + '.json') + + with open(debug_path, 'w', encoding='utf-8') as f: + f.write(json.dumps(output, sort_keys=True, ensure_ascii=False, indent=4, cls=NumpyEncoder)) + except Exception as e: + print("Error with Debug JSON: " + str(e)) def unpack_image(image, path, file_format='JPEG'): print('Leenkx Info: Unpacking to ' + path) @@ -488,10 +505,8 @@ def fetch_script_props(filename: str): # Property type is annotated if p_type is not None: - if p_type.startswith("iron.object."): - p_type = p_type[12:] - elif p_type.startswith("iron.math."): - p_type = p_type[10:] + if "." in p_type: + p_type = p_type.split(".")[-1] type_default_val = get_type_default_value(p_type) if type_default_val is None: @@ -717,7 +732,7 @@ def to_hex(val): def color_to_int(val) -> int: # Clamp values, otherwise the return value might not fit in 32 bit - # (and later cause problems, e.g. in the .arm file reader) + # (and later cause problems, e.g. in the .lnx file reader) val = [max(0.0, min(v, 1.0)) for v in val] return (int(val[3] * 255) << 24) + (int(val[0] * 255) << 16) + (int(val[1] * 255) << 8) + int(val[2] * 255) @@ -796,6 +811,8 @@ def safesrc(s): def safestr(s: str) -> str: """Outputs a string where special characters have been replaced with '_', which can be safely used in file and path names.""" + s = unicodedata.normalize('NFD', s) + s = "".join([c for c in s if not unicodedata.combining(c)]) for c in r'''[]/\;,><&*:§$%=+@!#^()|?^'"''': s = s.replace(c, '_') return ''.join([i if ord(i) < 128 else '_' for i in s]) @@ -938,10 +955,10 @@ def is_bone_animation_enabled(bobject): if not has_actions and adata != None: if hasattr(adata, 'nla_tracks') and adata.nla_tracks != None: for track in adata.nla_tracks: - if track.strips == None: + if track.strips == None or track.mute: continue for strip in track.strips: - if strip.action == None: + if strip.action == None or strip.mute: continue has_actions = True break @@ -957,7 +974,7 @@ def export_bone_data(bobject: bpy.types.Object) -> bool: return bobject.find_armature() and is_bone_animation_enabled(bobject) and get_rp().lnx_skin == 'On' def export_morph_targets(bobject: bpy.types.Object) -> bool: - if get_rp().lnx_morph_target != 'On': + if get_rp().lnx_morph_target != 'On' or bobject.type == 'CURVE': return False if not hasattr(bobject.data, 'shape_keys'): diff --git a/leenkx/blender/lnx/write_data.py b/leenkx/blender/lnx/write_data.py index 8c672e80..aadb4d95 100644 --- a/leenkx/blender/lnx/write_data.py +++ b/leenkx/blender/lnx/write_data.py @@ -246,6 +246,9 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + khafile.write(add_leenkx_library(sdk_path, 'leenkx', rel_path=do_relpath_sdk)) if not os.path.exists(os.path.join('Libraries', 'iron')): khafile.write(add_leenkx_library(sdk_path, 'iron', rel_path=do_relpath_sdk)) + if not os.path.exists(os.path.join('Libraries', 'lib/hscript')): + khafile.write(add_leenkx_library(sdk_path, 'lib/hscript', rel_path=do_relpath_sdk)) + khafile.write("project.addDefine('" + 'hscript' + "');\n") # Project libraries if os.path.exists('Libraries'): @@ -266,6 +269,7 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + if export_physics: assets.add_khafile_def('lnx_physics') + assets.add_khafile_def('lnx_physics_soft') if wrd.lnx_physics_engine == 'Bullet': assets.add_khafile_def('lnx_bullet') if not os.path.exists('Libraries/haxebullet'): @@ -329,14 +333,15 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + recastjs_path = recastjs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(recastjs_path, rel_path=do_relpath_sdk)) + if wrd.lnx_dce: + khafile.write("project.addParameter('-dce full');\n") + if wrd.lnx_no_traces: + khafile.write("project.addParameter('--no-traces');\n") + if wrd.lnx_asset_compression: + assets.add_khafile_def('lnx_compress') + if is_publish: assets.add_khafile_def('lnx_published') - if wrd.lnx_dce: - khafile.write("project.addParameter('-dce full');\n") - if wrd.lnx_no_traces: - khafile.write("project.addParameter('--no-traces');\n") - if wrd.lnx_asset_compression: - assets.add_khafile_def('lnx_compress') else: assets.add_khafile_def(f'lnx_assert_level={wrd.lnx_assert_level}') @@ -455,6 +460,10 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/debug_draw/line.frag.glsl", rel_path=do_relpath_sdk)) khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/debug_draw/line.vert.glsl", rel_path=do_relpath_sdk)) + if rpdat.rp_renderer == 'Forward': + khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/render_draw/render_line.frag.glsl", rel_path=do_relpath_sdk)) + khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/render_draw/render_line.vert.glsl", rel_path=do_relpath_sdk)) + if not is_publish and state.target == 'html5': khafile.write("project.addParameter('--debug');\n") @@ -515,7 +524,7 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + if os.path.exists(project_path + '/Bundled/config.lnx'): assets.add_khafile_def('lnx_config') - if is_publish and wrd.lnx_loadscreen: + if wrd.lnx_loadscreen: assets.add_khafile_def('lnx_loadscreen') if wrd.lnx_winresize or state.target == 'html5': @@ -632,7 +641,7 @@ def write_config(resx, resy): def write_mainhx(scene_name, resx, resy, is_play, is_publish): wrd = bpy.data.worlds['Lnx'] rpdat = lnx.utils.get_rp() - scene_ext = '.lz4' if (wrd.lnx_asset_compression and is_publish) else '' + scene_ext = '.lz4' if (wrd.lnx_asset_compression) else '' if scene_ext == '' and not wrd.lnx_minimize: scene_ext = '.json' winmode = get_winmode(wrd.lnx_winmode) @@ -706,13 +715,20 @@ class Main { if rpdat.lnx_shadowmap_bounds != 1.0: f.write("\n" + content_indent + "iron.object.LightObject.cascadeBounds = " + str(rpdat.lnx_shadowmap_bounds) + ";") - if is_publish and wrd.lnx_loadscreen: - asset_references = list(set(assets.assets)) + asset_references = list(set(assets.assets)) + + if wrd.lnx_loadscreen: loadscreen_class = 'leenkx.trait.internal.LoadingScreen' if os.path.isfile(lnx.utils.get_fp() + '/Sources/' + wrd.lnx_project_package + '/LoadingScreen.hx'): loadscreen_class = wrd.lnx_project_package + '.LoadingScreen' f.write("\n" + content_indent + "leenkx.system.Starter.numAssets = " + str(len(asset_references)) + ";") f.write("\n" + content_indent + "leenkx.system.Starter.drawLoading = " + loadscreen_class + ".render;") + f.write("\n" + content_indent + "leenkx.system.Starter.assets = " + str([os.path.basename(path) for path in asset_references if 'Bundled' in path]) + ";") + + scenes_list = [f"'{scene.name}'" for scene in bpy.data.scenes if getattr(scene, 'lnx_export', True)] + scenes_array = "[" + ", ".join(scenes_list) + "]" + + f.write("\n" + content_indent + "leenkx.system.Starter.scenes = " + scenes_array + ";") if wrd.lnx_ui == 'Enabled': if wrd.lnx_canvas_img_scaling_quality == 'low': @@ -759,6 +775,7 @@ def write_indexhtml(w, h, is_publish): if wrd.lnx_project_html5_popupmenu_in_browser: popupmenu_in_browser = ' oncontextmenu="return false"' with open(lnx.utils.build_dir() + dest + '/index.html', 'w') as f: + icon = """""" if wrd.lnx_project_icon != '' else '' f.write( """ @@ -769,6 +786,7 @@ def write_indexhtml(w, h, is_publish): """) f.write(""" """+html.escape( wrd.lnx_project_name)+""" + """+icon+""" """) @@ -964,6 +982,11 @@ const vec3 compoLetterboxColor = vec3(""" + str(round(rpdat.lnx_letterbox_color[ if rpdat.lnx_distort: f.write( """const float compoDistortStrength = """ + str(round(rpdat.lnx_distort_strength * 100) / 100) + """; +""") + + if rpdat.lnx_fisheye: + f.write( +"""const float compoFisheyeStrength = """ + str(round(rpdat.lnx_fisheye_strength * 100) / 100) + """; """) if rpdat.lnx_grain: @@ -1054,17 +1077,13 @@ const float voxelgiRefr = """ + str(round(rpdat.lnx_voxelgi_refr * 100) / 100) + """const int skinMaxBones = """ + str(rpdat.lnx_skin_max_bones) + """; """) - if '_Clusters' in wrd.world_defs: - max_lights = "4" - max_lights_clusters = "4" - if rpdat.rp_shadowmap_atlas: - max_lights = str(rpdat.rp_max_lights) - max_lights_clusters = str(rpdat.rp_max_lights_cluster) - # prevent max lights cluster being higher than max lights - if (int(max_lights_clusters) > int(max_lights)): - max_lights_clusters = max_lights + max_lights = str(rpdat.rp_max_lights) + max_lights_clusters = str(rpdat.rp_max_lights_cluster) + # prevent max lights cluster being higher than max lights + if (int(max_lights_clusters) > int(max_lights)): + max_lights_clusters = max_lights - f.write( + f.write( """const int maxLights = """ + max_lights + """; const int maxLightsCluster = """ + max_lights_clusters + """; const float clusterNear = 3.0; diff --git a/lib/hscript/.github/workflows/main.yml b/lib/hscript/.github/workflows/main.yml new file mode 100644 index 00000000..026aaad2 --- /dev/null +++ b/lib/hscript/.github/workflows/main.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + # This should disable running the workflow on tags + branches: + - "**" + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: krdlab/setup-haxe@v1 + with: + haxe-version: 4.3.4 + - name: Install haxelib dependencies + run: | + haxelib install hx3compat + haxelib dev hscript . + - name: Test interp + run: | + haxe bin/build-interp.hxml + haxe bin/build-interp.hxml -D hscriptPos + - name: Test neko + run: | + haxe bin/build-neko.hxml && neko bin/Test.n + haxe bin/build-neko.hxml -D hscriptPos && neko bin/Test.n + - name: Test js + run: | + haxe bin/build-js.hxml && node bin/Test.js + haxe bin/build-js.hxml -D hscriptPos && node bin/Test.js + - name: Build hashlink from source + run: | + git clone https://github.com/HaxeFoundation/hashlink.git + cd hashlink + make hl + cd .. + - name: Test hl + run: | + haxe bin/build-hl.hxml && ./hashlink/hl bin/Test.hl + haxe bin/build-hl.hxml -D hscriptPos && ./hashlink/hl bin/Test.hl diff --git a/lib/hscript/.gitignore b/lib/hscript/.gitignore new file mode 100644 index 00000000..ace37e65 --- /dev/null +++ b/lib/hscript/.gitignore @@ -0,0 +1,2 @@ +/hscript.swf +/release.zip diff --git a/lib/hscript/.vscode/launch.json b/lib/hscript/.vscode/launch.json new file mode 100644 index 00000000..be0d90f9 --- /dev/null +++ b/lib/hscript/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "HashLink (launch)", + "request": "launch", + "type": "hl", + "hxml": "hscript.hxml", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build" + } + ] +} \ No newline at end of file diff --git a/lib/hscript/.vscode/tasks.json b/lib/hscript/.vscode/tasks.json new file mode 100644 index 00000000..0d772d54 --- /dev/null +++ b/lib/hscript/.vscode/tasks.json @@ -0,0 +1,16 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "hxml", + "file": "hscript.hxml", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/lib/hscript/LICENSE b/lib/hscript/LICENSE new file mode 100644 index 00000000..d89310ae --- /dev/null +++ b/lib/hscript/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (C)2008-2017 Haxe Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION 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/lib/hscript/README.md b/lib/hscript/README.md new file mode 100644 index 00000000..d81ab5d3 --- /dev/null +++ b/lib/hscript/README.md @@ -0,0 +1,100 @@ +hscript +======= + +Parse and evalutate Haxe expressions. + + +In some projects it's sometimes useful to be able to interpret some code dynamically, without recompilation. + +Haxe script is a complete subset of the Haxe language. + +It is dynamically typed but allows all Haxe expressions apart from type (class,enum,typedef) declarations. + +Usage +----- + +```haxe +var expr = "var x = 4; 1 + 2 * x"; +var parser = new hscript.Parser(); +var ast = parser.parseString(expr); +var interp = new hscript.Interp(); +trace(interp.execute(ast)); +``` + +In case of a parsing error an `hscript.Expr.Error` is thrown. You can use `parser.line` to check the line number. + +You can set some globaly accessible identifiers by using `interp.variables.set("name",value)` + +Example +------- + +Here's a small example of Haxe Script usage : +```haxe +var script = " + var sum = 0; + for( a in angles ) + sum += Math.cos(a); + sum; +"; +var parser = new hscript.Parser(); +var program = parser.parseString(script); +var interp = new hscript.Interp(); +interp.variables.set("Math",Math); // share the Math class +interp.variables.set("angles",[0,1,2,3]); // set the angles list +trace( interp.execute(program) ); +``` + +This will calculate the sum of the cosines of the angles given as input. + +Haxe Script has not been really optimized, and it's not meant to be very fast. But it's entirely crossplatform since it's pure Haxe code (it doesn't use any platform-specific API). + +Advanced Usage +-------------- + +When compiled with `-D hscriptPos` you will get fine error reporting at parsing time. + +You can subclass `hscript.Interp` to override behaviors for `get`, `set`, `call`, `fcall` and `cnew`. + +You can add more binary and unary operations to the parser by setting `opPriority`, `opRightAssoc` and `unops` content. + +You can use `parser.allowJSON` to allow JSON data. + +You can use `parser.allowTypes` to parse types for local vars, exceptions, function args and return types. Types are ignored by the interpreter. + +You can use `parser.allowMetadata` to parse metadata before expressions on in anonymous types. Metadata are ignored by the interpreter. + +You can use `new hscript.Macro(pos).convert(ast)` to convert an hscript AST to a Haxe macros one. + +You can use `hscript.Checker` in order to type check and even get completion, using `haxe -xml` output for type information. + +Limitations +----------- + +Compared to Haxe, limitations are : + +- `switch` construct is supported but not pattern matching (no variable capture, we use strict equality to compare `case` values and `switch` value) +- only one variable declaration is allowed in `var` +- the parser supports optional types for `var` and `function` if `allowTypes` is set, but the interpreter ignores them +- you can enable per-expression position tracking by compiling with `-D hscriptPos` +- you can parse some type declarations (import, class, typedef, etc.) with parseModule + +Install +------- + +In order to install Haxe Script, use `haxelib install hscript` and compile your program with `-lib hscript`. + +These are the main required files in hscript : + + - `hscript.Expr` : contains enums declarations + - `hscript.Parser` : a small parser that turns a string into an expression structure (AST) + - `hscript.Interp` : a small interpreter that execute the AST and returns the latest evaluated value + +Some other optional files : + + - `hscript.Async` : converts Expr into asynchronous version + - `hscript.Bytes` : Expr serializer/unserializer + - `hscript.Checker` : type checking and completion for hscript Expr + - `hscript.Macro` : convert Haxe macro into hscript Expr + - `hscript.Printer` : convert hscript Expr to String + - `hscript.Tools` : utility functions (map/iter) + diff --git a/lib/hscript/TestHScript.hx b/lib/hscript/TestHScript.hx new file mode 100644 index 00000000..da3c603a --- /dev/null +++ b/lib/hscript/TestHScript.hx @@ -0,0 +1,263 @@ +import haxe.ds.EnumValueMap; +import haxe.ds.Option; +import hscript.Macro; +import hscript.Tools; +import hscript.Async; +import hscript.Printer; +import hscript.Checker; +import haxe.unit.*; + +class TestHScript extends TestCase { + + var optimize : Bool; + + function new(optimize=false) { + this.optimize = optimize; + super(); + } + + function assertScript(x,v:Dynamic,?vars : Dynamic, allowTypes=false, ?pos:haxe.PosInfos) { + var p = new hscript.Parser(); + p.allowTypes = allowTypes; + var program = p.parseString(x); + var bytes = hscript.Bytes.encode(program); + program = hscript.Bytes.decode(bytes); + var interp = new hscript.Interp(); + #if js + if( optimize ) interp = new hscript.JsInterp(); + #end + if( vars != null ) + for( v in Reflect.fields(vars) ) + interp.variables.set(v,Reflect.field(vars,v)); + var ret : Dynamic = interp.execute(program); + assertEquals(v, ret, pos); + } + + function test():Void { + assertScript("0",0); + assertScript("0xFF", 255); + #if !(php || python) + #if haxe3 + assertScript("0xBFFFFFFF", 0xBFFFFFFF); + assertScript("0x7FFFFFFF", 0x7FFFFFFF); + #elseif !neko + assertScript("n(0xBFFFFFFF)", 0xBFFFFFFF, { n : haxe.Int32.toNativeInt }); + assertScript("n(0x7FFFFFFF)", 0x7FFFFFFF, { n : haxe.Int32.toNativeInt } ); + #end + #end + assertScript("-123",-123); + assertScript("- 123",-123); + assertScript("1.546",1.546); + assertScript(".545",.545); + assertScript("1e5",100000); + assertScript("1.2e2",120); + assertScript("100e-2",1); + assertScript("1.2e-1",0.12); + assertScript("'bla'","bla"); + assertScript("null",null); + assertScript("true",true); + assertScript("false",false); + assertScript("1 == 2",false); + assertScript("1.3 == 1.3",true); + assertScript("5 > 3",true); + assertScript("0 < 0",false); + assertScript("-1 <= -1",true); + assertScript("1 + 2",3); + assertScript("~545",-546); + assertScript("'abc' + 55","abc55"); + assertScript("'abc' + 'de'","abcde"); + assertScript("-1 + 2",1); + assertScript("1 / 5",0.2); + assertScript("3 * 2 + 5",11); + assertScript("3 * (2 + 5)",21); + assertScript("3 * 2 // + 5 \n + 6",12); + assertScript("3 /* 2\n */ + 5",8); + assertScript("[55,66,77][1]",66); + assertScript("[11,22,33,][2]", 33); + assertScript("var a = [55]; a[0] *= 2; a[0]",110); + assertScript("x",55,{ x : 55 }); + assertScript("var y = 33; y",33); + assertScript("{ 1; 2; 3; }",3); + assertScript("{ var x = 0; } x",55,{ x : 55 }); + assertScript("o.val",55,{ o : { val : 55 } }); + assertScript("o.val",null,{ o : {} }); + assertScript("var a = 1; a++",1); + assertScript("var a = 1; a++; a",2); + assertScript("var a = 1; ++a",2); + assertScript("var a = 1; a *= 3",3); + assertScript("a = b = 3; a + b",6); + assertScript("add(1,2)",3,{ add : function(x,y) return x + y }); + assertScript("a.push(5); a.pop() + a.pop()",8,{ a : [3] }); + assertScript("if( true ) 1 else 2",1); + assertScript("if( false ) 1 else 2",2); + assertScript("var t = 0; for( x in [1,2,3] ) t += x; t",6); + assertScript("var a = new Array(); for( x in 0...5 ) a[x] = x; a.join('-')","0-1-2-3-4"); + assertScript("(function(a,b) return a + b)(4,5)",9); + assertScript("var y = 0; var add = function(a) y += a; add(5); add(3); y", 8); + assertScript("var a = [1,[2,[3,[4,null]]]]; var t = 0; while( a != null ) { t += a[0]; a = a[1]; }; t",10); + assertScript("var a = false; do { a = true; } while (!a); a;",true); + assertScript("var t = 0; for( x in 1...10 ) t += x; t", 45); + assertScript("var t = 0; for( x in new IntIterator(1,10) ) t +=x; t", 45); + assertScript("var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x",790); + assertScript("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",56); + assertScript("var i=2; if( true ) --i; i",1); + assertScript("var i=0; if( i++ > 0 ) i=3; i",1); + assertScript("var a = 5/2; a",2.5); + assertScript("{ x = 3; x; }", 3); + assertScript("{ x : 3, y : {} }.x", 3); + assertScript("function bug() return { \n }\nbug().x", null); + assertScript("1 + 2 == 3", true); + assertScript("-2 == 3 - 5", true); + assertScript("var x=-3; x", -3); + assertScript("var a:Array=[1,2,4]; a[2]", 4, null, true); + assertScript("/**/0", 0); + assertScript("x=1;x*=-2", -2); + assertScript("var f = x -> x + 1; f(3)", 4); + assertScript("var f = () -> 55; f()", 55); + assertScript("var f = (x) -> x + 1; f(3)", 4); + assertScript("var f = (x:Int) -> x + 1; f(3)", 4); + assertScript("var f = (x,y) -> x + y; f(3,1)", 4); + assertScript("var f = (x,y:Int) -> x + y; f(3,1)", 4); + assertScript("var f = (x:Int,y:Int) -> x + y; f(3,1)", 4); + assertScript("var f:Int->Int->Int = (x:Int,y:Int) -> x + y; f(3,1)", 4, null, true); + assertScript("var f:(x:Int, y:Int)->Int = (x:Int,y:Int) -> x + y; f(3,1)", 4, null, true); + assertScript("var f:(x:Int)->(y:Int, z:Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2)", 6, null, true); + assertScript("var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2)", 6, null, true); + assertScript("var a = 10; var b = 5; a - -b", 15); + assertScript("var a = 10; var b = 5; a - b / 2", 7.5); + assertScript("var a; a", null); + assertScript("var a = 1, b = 5; a + b;", 6); + assertScript("var a, b = 5; if (a == null) a = 2; a + b;", 7); + assertScript("var a:Int; a", null, null, true); + assertScript("var a:Int = 1, b:Int = 5; a + b;", 6, null, true); + assertScript("var a:Int, b:Int = 5; if (a == null) a = 2; a + b;", 7, null, true); + assertScript("false && xxx", false); + assertScript("true || xxx", true); + assertScript("[for( x in arr ) switch( x ) { case 1: 55; case 3: 66; default: 0; }].join(':')",'55:0:66',{ arr : [1,2,3] }); + assertScript("switch( x ) { case 1: 55; case 3: 66; default: 0; }",66 ,{ x : 3 }); + assertScript("var a = 1; switch( b ) { default: a = 2; }; a", 2, { b : 2 }); + assertScript("var a = 1; switch( b ) { case 2: a = 100; default: a = 2; }; a", 100, { b : 2 }); + assertScript("var a = 3; switch( b ) { case 2: if (a == 1) { a = 100; } else { a = 99; }; default: a = 2; }; a", 99, { b : 2 }); + } + + function testNullFieldAccess():Void { + var pt = {x : 10}; + var vars = { + ptnull : null, + pt: pt, + pt2null : {pt : null}, + pt2: {pt : pt} + } + assertScript("ptnull?.x", null, vars); + assertScript("pt?.x", 10, vars); + assertScript("pt2null?.pt", null, vars); + assertScript("pt2null?.pt?.x", null, vars); + assertScript("pt2?.pt", pt, vars); + assertScript("pt2?.pt?.x", 10, vars); + } + + function testIsOperator():Void { + var vars = { + String: String, + Bool: Bool, + Int: Int, + Float: Float, + Dynamic: Dynamic + } + assertScript("10 is Int", true, vars); + assertScript("10.0 is Int", true, vars); + assertScript("10.1 is Int", false, vars); + assertScript("10 is Float", true, vars); + assertScript("10.0 is Float", true, vars); + assertScript("10.1 is Float", true, vars); + assertScript("10 is String", false, vars); + assertScript('"hscript" is String', true, vars); + assertScript('"" is String', true, vars); + assertScript('true is Bool', true, vars); + assertScript('false is Bool', true, vars); + assertScript('0 is Bool', false, vars); + assertScript('1 is Bool', false, vars); + assertScript('1 is Bool', false, vars); + assertScript("10 is Dynamic", true, vars); + assertScript("10.1 is Dynamic", true, vars); + assertScript('"hscript" is Dynamic', true, vars); + assertScript('null is Int', false, vars); + assertScript('null is Float', false, vars); + assertScript('null is String', false, vars); + assertScript('null is Bool', false, vars); + assertScript('null is Dynamic', false, vars); + } + + function testMap():Void { + var objKey = { ok:true }; + var vars = { + stringMap: ["foo" => "Foo", "bar" => "Bar"], + intMap:[100 => "one hundred"], + objKey: objKey, + objMap:[objKey => "ok"], + enumKey:Option.Some("some"), + enumMap:new EnumValueMap, String>(), + stringIntMap: ["foo" => 100] + } + vars.enumMap.set(vars.enumKey, "ok"); + + assertScript('stringMap["foo"]', "Foo", vars); + assertScript('intMap[100]', "one hundred", vars); + assertScript('objMap[objKey]', "ok", vars); + assertScript('enumMap[enumKey]', "ok", vars); + assertScript('stringMap["a"] = "A"; stringMap["a"]', "A", vars); + assertScript('intMap[200] = objMap[{foo:false}] = enumMap[enumKey] = "A"', "A", vars); + assertEquals('A', vars.intMap[200]); + assertEquals('A', vars.enumMap.get(vars.enumKey)); + for (key in vars.objMap.keys()) { + if (key != objKey) { + assertEquals(false, (key:Dynamic).foo); + assertEquals('A', vars.objMap[key]); + } + } + + assertScript(' + var keys = []; + for (key in stringMap.keys()) keys.push(key); + keys.join("_"); + ', { + var keys = []; + for (key in vars.stringMap.keys()) keys.push(key); + keys.join("_"); + }, vars); + assertScript('stringMap.remove("foo"); stringMap.exists("foo");', false, vars); + assertScript('stringMap["foo"] = "a"; stringMap["foo"] += "b"', 'ab', vars); + assertEquals('ab', vars.stringMap['foo']); + assertScript('stringIntMap["foo"]++', 100, vars); + assertEquals(101, vars.stringIntMap['foo']); + assertScript('++stringIntMap["foo"]', 102, vars); + assertScript('var newMap = ["foo"=>"foo"]; newMap["foo"];', 'foo', vars); + #if (!php || (haxe_ver >= 3.3)) + assertScript('var newMap = [enumKey=>"foo"]; newMap[enumKey];', 'foo', vars); + #end + assertScript('var newMap = [{a:"a"}=>"foo", objKey=>"bar"]; newMap[objKey];', 'bar', vars); + } + + static function main() { + #if ((haxe_ver < 4) && php) + // uncaught exception: The each() function is deprecated. This message will be suppressed on further calls (errno: 8192) + // in file: /Users/travis/build/andyli/hscript/bin/lib/Type.class.php line 178 + untyped __php__("error_reporting(E_ALL ^ E_DEPRECATED);"); + #end + + var runner = new TestRunner(); + runner.add(new TestHScript()); + runner.add(new TestHScript(true)); + var succeed = runner.run(); + + #if sys + Sys.exit(succeed ? 0 : 1); + #elseif flash + flash.system.System.exit(succeed ? 0 : 1); + #else + if (!succeed) + throw "failed"; + #end + } + +} diff --git a/lib/hscript/bin/.gitignore b/lib/hscript/bin/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/lib/hscript/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/lib/hscript/extraParams.hxml b/lib/hscript/extraParams.hxml new file mode 100644 index 00000000..607ada6f --- /dev/null +++ b/lib/hscript/extraParams.hxml @@ -0,0 +1 @@ +--macro keep('IntIterator') \ No newline at end of file diff --git a/lib/hscript/haxelib.json b/lib/hscript/haxelib.json new file mode 100644 index 00000000..1d90e670 --- /dev/null +++ b/lib/hscript/haxelib.json @@ -0,0 +1,9 @@ +{ + "name": "hscript", + "url": "https://github.com/HaxeFoundation/hscript", + "license": "MIT", + "description": "Haxe Script is a scripting engine for a subset of the Haxe language", + "version": "2.6.0", + "releasenote": "More Haxe 4/5 features", + "contributors": ["ncannasse","HaxeFoundation"] +} diff --git a/lib/hscript/hscript.hxml b/lib/hscript/hscript.hxml new file mode 100644 index 00000000..dba8f5ed --- /dev/null +++ b/lib/hscript/hscript.hxml @@ -0,0 +1,2 @@ +bin/build-each.hxml +-hl bin/Test.hl \ No newline at end of file diff --git a/lib/hscript/hscript/Async.hx b/lib/hscript/hscript/Async.hx new file mode 100644 index 00000000..c40402b2 --- /dev/null +++ b/lib/hscript/hscript/Async.hx @@ -0,0 +1,536 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr; + + +enum VarMode { + Defined; + ForceSync; +} + +class Async { + + var definedVars : Array<{ n : String, prev : Null }>; + var vars : Map; + var currentFun : String; + var currentLoop : Expr; + var currentBreak : Expr -> Expr; + var uid = 0; + public var asyncIdents : Map; + + static var nullExpr : Expr = #if hscriptPos { e : null, pmin : 0, pmax : 0, origin : "", line : 0 } #else null #end; + static var nullId = mk(EIdent("null"), nullExpr); + + inline static function expr( e : Expr ) { + return #if hscriptPos e.e #else e #end; + } + + inline static function mk( e, inf : Expr ) : Expr { + return #if hscriptPos { e : e, pmin : inf.pmin, pmax : inf.pmax, origin : inf.origin, line : inf.line } #else e #end; + } + + /** + Convert a script into asynchronous one. + - calls such as foo(a,b,c) are translated to a_foo(function(r) ...rest, a,b,c) where r is the result value + - object access such obj.bar(a,b,c) are translated to obj.a_bar(function(r) ...rest, a, b, c) + - @async expr will execute the expression but continue without waiting for it to finish + - @split [ e1, e2, e3 ] is transformed to split(function(_) ...rest, [e1, e2, e3]) which + should execute asynchronously all expressions - until they return - before continuing the execution + - for(i in v) block; loops are translated to the following: + var _i = makeIterator(v); + function _loop() { + if( !_i.hasNext() ) return; + var v = _i.next(); + block(function(_) _loop()); + } + _loop() + - while loops are translated similar to for loops + - break and continue are correctly handled + - you can use @sync to disable async transformation in some code parts (for performance reason) + - a few expressions are still not supported (complex calls, try/catch, and a few others) + + In these examples ...rest represents the continuation of execution of the script after the expression + **/ + public static function toAsync( e : Expr, topLevelSync = false ) { + var a = new Async(); + return a.build(e, topLevelSync); + } + + public dynamic function getTopLevelEnd() { + return ignore(); + } + + public function build( e : Expr, topLevelSync = false ) { + if( topLevelSync ) { + return buildSync(e,null); + } else { + var end = getTopLevelEnd(); + return toCps(e, end, end); + } + } + + function defineVar( v : String, mode ) { + definedVars.push({ n : v, prev : vars.get(v) }); + vars.set(v, mode); + } + + function lookupFunctions( el : Array ) { + for( e in el ) + switch( expr(e) ) { + case EFunction(_, _, name, _) if( name != null ): defineVar(name, Defined); + case EMeta("sync",_,expr(_) => EFunction(_,_,name,_)) if( name != null ): defineVar(name, ForceSync); + default: + } + } + + function buildSync( e : Expr, exit : Expr ) : Expr { + switch( expr(e) ) { + case EFunction(_,_,name,_): + if( name != null ) + return toCps(e, null, null); + return e; + case EBlock(el): + var v = saveVars(); + lookupFunctions(el); + var e = block([for(e in el) buildSync(e,exit)], e); + restoreVars(v); + return e; + case EMeta("async", _, e): + return toCps(e, ignore(), ignore()); + case EMeta("sync", args, ef = expr(_) => EFunction(fargs, body, name, ret)): + return mk(EMeta("sync",args,mk(EFunction(fargs, buildSync(body,null), name, ret),ef)),e); + case EBreak if( currentBreak != null ): + return currentBreak(e); + case EContinue if( currentLoop != null ): + return block([retNull(currentLoop, e), mk(EReturn(),e)],e); + case EFor(_), EWhile(_): + var oldLoop = currentLoop, oldBreak = currentBreak; + currentLoop = null; + currentBreak = null; + e = Tools.map(e, buildSync.bind(_, exit)); + currentLoop = oldLoop; + currentBreak = oldBreak; + return e; + case EReturn(eret) if( exit != null ): + return block([eret == null ? retNull(exit, e) : call(exit,[eret], e), mk(EReturn(),e)], e); + default: + return Tools.map(e, buildSync.bind(_, exit)); + } + } + + public function new() { + vars = new Map(); + definedVars = []; + } + + function ignore(?e) : Expr { + var inf = e == null ? nullExpr : e; + return fun("_", block(e == null ? [] : [e],inf)); + } + + inline function ident(str, e) { + return mk(EIdent(str), e); + } + + inline function fun(arg:String, e, ?name) { + return mk(EFunction([{ name : arg, t : null }], e, name), e); + } + + inline function funs(arg:Array, e, ?name) { + return mk(EFunction([for( a in arg ) { name : a, t : null }], e, name), e); + } + + inline function block(arr:Array, e) { + if( arr.length == 1 && expr(arr[0]).match(EBlock(_)) ) + return arr[0]; + return mk(EBlock(arr), e); + } + + inline function field(e, f, inf) { + return mk(EField(e, f), inf); + } + + inline function binop(op, e1, e2, inf) { + return mk(EBinop(op, e1, e2), inf); + } + + inline function call(e, args, inf) { + return mk(ECall(e, args), inf); + } + + function retNull(e:Expr,?pos) : Expr { + switch( expr(e) ) { + case EFunction([{name:"_"}], e, _, _): return e; + default: + } + return call(e, [nullId], pos == null ? e : pos); + } + + function makeCall( ecall, args : Array, rest : Expr, exit, sync = false ) { + var names = [for( i in 0...args.length ) "_a"+uid++]; + var rargs = [for( i in 0...args.length ) ident(names[i],ecall)]; + if( !sync ) + rargs.unshift(rest); + var rest = mk(sync ? ECall(rest,[call(ecall, rargs,ecall)]) : ECall(ecall, rargs), ecall); + var i = args.length - 1; + while( i >= 0 ) { + rest = toCps(args[i], fun(names[i], rest), exit); + i--; + } + return rest; + } + + var syncFlag : Bool; + + function isSync( e : Expr ) { + syncFlag = true; + checkSync(e); + return syncFlag; + } + + inline function isAsyncIdent( id : String ) { + return asyncIdents == null || asyncIdents.exists(id); + } + + function checkSync( e : Expr ) { + if( !syncFlag ) + return; + switch( expr(e) ) { + case ECall(expr(_) => EIdent(i),_) if( isAsyncIdent(i) || vars.get(i) == Defined ): + syncFlag = false; + case ECall(expr(_) => EField(_,i),_) if( isAsyncIdent(i) ): + syncFlag = false; + case EFunction(_,_,name,_) if( name != null ): + syncFlag = false; + case EMeta("sync" | "async", _, _): + // isolated from the sync part + default: + Tools.iter(e, checkSync); + } + } + + function saveVars() { + return definedVars.length; + } + + function restoreVars(k) { + while( definedVars.length > k ) { + var v = definedVars.pop(); + if( v.prev == null ) vars.remove(v.n) else vars.set(v.n, v.prev); + } + } + + public function toCps( e : Expr, rest : Expr, exit : Expr ) : Expr { + if( isSync(e) ) + return call(rest, [buildSync(e, exit)],e); + switch( expr(e) ) { + case EBlock(el): + var el = el.copy(); + var vold = saveVars(); + lookupFunctions(el); + while( el.length > 0 ) { + var e = toCps(el.pop(), rest, exit); + rest = ignore(e); + } + restoreVars(vold); + return retNull(rest); + case EFunction(args, body, name, t): + var vold = saveVars(); + if( name != null ) + defineVar(name, Defined); + for( a in args ) + defineVar(a.name, Defined); + args.unshift( { name : "_onEnd", t : null } ); + var frest = ident("_onEnd",e); + var oldFun = currentFun; + currentFun = name; + var body = toCps(body, frest, frest); + var f = mk(EFunction(args, body, name, t),e); + restoreVars(vold); + return rest == null ? f : call(rest, [f],e); + case EParent(e): + return mk(EParent(toCps(e, rest, exit)),e); + case EMeta("sync", _, e): + return call(rest,[buildSync(e,exit)],e); + case EMeta("async", _, e): + var nothing = ignore(); + return block([toCps(e,nothing,nothing),retNull(rest)],e); + case EMeta("split", _, e): + var args = switch( expr(e) ) { case EArrayDecl(el): el; default: throw "@split expression should be an array"; }; + var args = [for( a in args ) fun("_rest", toCps(block([a],a), ident("_rest",a), exit))]; + return call(ident("split",e), [rest, mk(EArrayDecl(args),e)],e); + case ECall(expr(_) => EIdent(i), args): + var mode = vars.get(i); + return makeCall( ident( mode != null ? i : "a_" + i,e) , args, rest, exit, mode == ForceSync); + case ECall(expr(_) => EField(e, f), args): + return makeCall(field(e,"a_"+f,e), args, rest, exit); + case EFor(v, eit, eloop): + var id = ++uid; + var it = ident("_i" + id,e); + var oldLoop = currentLoop, oldBreak = currentBreak; + var loop = ident("_loop" + id,e); + currentLoop = loop; + currentBreak = function(inf) return block([retNull(rest, inf), mk(EReturn(),inf)], inf); + var efor = block([ + mk(EVar("_i" + id, call(ident("makeIterator",eit),[eit],eit)),eit), + fun("_", block([ + mk(EIf(mk(EUnop("!", true, call( field(it, "hasNext", it), [], it)),it), currentBreak(it)),it), + mk(EVar(v, call(field(it, "next",it), [], it)), it), + toCps(eloop, loop, exit), + ], it),"_loop" + id), + retNull(loop, e), + ], e); + currentLoop = oldLoop; + currentBreak = oldBreak; + return efor; + case EUnop(op = "!", prefix, eop): + return toCps(eop, fun("_r",call(rest, [mk(EUnop(op, prefix, ident("_r",e)),e)], e)), exit); + case EBinop(op, e1, e2): + switch( op ) { + case "=", "+=", "-=", "/=", "*=", "%=", "&=", "|=", "^=": + switch( expr(e1) ) { + case EIdent(_): + var id = "_r" + uid++; + return toCps(e2, fun(id, call(rest, [binop(op, e1, ident(id,e1),e1)], e1)), exit); + case EField(ef1, f): + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(ef1, fun(id1, toCps(e2, fun(id2, call(rest, [binop(op, field(ident(id1, e1), f, ef1), ident(id2, e2), e)], e)), exit)), exit); + case EArray(earr, eindex): + var idArr = "_r" + uid++; + var idIndex = "_r" + uid++; + var idVal = "_r" + uid++; + return toCps(earr,fun(idArr, toCps(eindex, fun(idIndex, toCps(e2, + fun(idVal, call(rest, [binop(op, mk(EArray(ident(idArr,earr), ident(idIndex,eindex)),e1), ident(idVal,e1), e)], e)) + , exit)), exit)),exit); + default: + throw "assert " + e1; + } + case "||": + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(e1, fun(id1, mk(EIf(binop("==", ident(id1,e1), ident("true",e1), e1),call(rest,[ident("true",e1)],e1),toCps(e2, rest, exit)),e)), exit); + case "&&": + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(e1, fun(id1, mk(EIf(binop("!=", ident(id1,e1), ident("true",e1), e1),call(rest,[ident("false",e1)],e1),toCps(e2, rest, exit)),e)), exit); + default: + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(e1, fun(id1, toCps(e2, fun(id2, call(rest, [binop(op, ident(id1,e1), ident(id2,e2), e)], e)), exit)), exit); + } + case EIf(cond, e1, e2), ETernary(cond, e1, e2): + return toCps(cond, fun("_c", mk(EIf(ident("_c",cond), toCps(e1, rest, exit), e2 == null ? retNull(rest) : toCps(e2, rest, exit)),e)), exit); + case EWhile(cond, ewh): + var id = ++uid; + var loop = ident("_loop" + id, cond); + var oldLoop = currentLoop, oldBreak = currentBreak; + currentLoop = loop; + currentBreak = function(e) return block([retNull(rest,e), mk(EReturn(),e)],e); + var ewhile = block([ + fun("_r", + toCps(cond, fun("_c", mk(EIf(ident("_c", cond), toCps(ewh, loop, exit), retNull(rest,cond)),cond)), exit) + , "_loop"+id), + retNull(loop, cond), + ],e); + currentLoop = oldLoop; + currentBreak = oldBreak; + return ewhile; + case EReturn(eret): + return eret == null ? retNull(exit, e) : toCps(eret, exit, exit); + case EObject(fields): + var id = "_o" + uid++; + var rest = call(rest, [ident(id,e)], e); + fields.reverse(); + for( f in fields ) + rest = toCps(f.e, fun("_r", block([ + binop("=", mk(EField(ident(id,f.e), f.name),f.e), ident("_r",f.e), f.e), + rest, + ],f.e)),exit); + return block([ + mk(EVar(id, mk(EObject([]),e)),e), + rest, + ],e); + case EArrayDecl(el): + var id = "_a" + uid++; + var rest = call(rest, [ident(id,e)], e); + var i = el.length - 1; + while( i >= 0 ) { + var e = el[i]; + rest = toCps(e, fun("_r", block([ + binop("=", mk(EArray(ident(id,e), mk(EConst(CInt(i)),e)),e), ident("_r",e), e), + rest, + ],e)), exit); + i--; + } + return block([ + mk(EVar(id, mk(EArrayDecl([]),e)),e), + rest, + ],e); + case EArray(earr, eindex): + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(earr, fun(id1, toCps(eindex, fun(id2, call(rest, [mk(EArray(ident(id1,e), ident(id2,e)),e)], e)), exit)), exit); + case EVar(v, t, ev): + if( ev == null ) + return block([e, retNull(rest, e)], e); + return block([ + mk(EVar(v, t),e), + toCps(ev, fun("_r", block([binop("=", ident(v,e), ident("_r",e), e), retNull(rest,e)], e)), exit), + ],e); + case EConst(_), EIdent(_), EUnop(_), EField(_): + return call(rest, [e], e); + case ENew(cl, args): + var names = [for( i in 0...args.length ) "_a"+uid++]; + var rargs = [for( i in 0...args.length ) ident(names[i], args[i])]; + var rest = call(rest,[mk(ENew(cl, rargs),e)],e); + var i = args.length - 1; + while( i >= 0 ) { + rest = toCps(args[i], fun(names[i], rest), exit); + i--; + } + return rest; + case EBreak: + if( currentBreak == null ) throw "Break outside loop"; + return currentBreak(e); + case EContinue: + if( currentLoop == null ) throw "Continue outside loop"; + return block([retNull(currentLoop, e), mk(EReturn(),e)], e); + case ESwitch(v, cases, def): + var cases = [for( c in cases ) { values : c.values, expr : toCps(c.expr, rest, exit) } ]; + return toCps(v, mk(EFunction([ { name : "_c", t : null } ], mk(ESwitch(ident("_c",v), cases, def == null ? retNull(rest) : toCps(def, rest, exit)),e)),e), exit ); + case EThrow(v): + return toCps(v, mk(EFunction([ { name : "_v", t : null } ], mk(EThrow(v),v)), v), exit); + case EMeta(name,_,e) if( name.charCodeAt(0) == ":".code ): // ignore custom ":" metadata + return toCps(e, rest, exit); + //case EDoWhile(_), ETry(_), ECall(_): + default: + throw "Unsupported async expression " + Printer.toString(e); + } + } + +} + + +class AsyncInterp extends Interp { + + public function setContext( api : Dynamic ) { + + var funs = new Array(); + for( v in variables.keys() ) + if( Reflect.isFunction(variables.get(v)) ) + funs.push({ v : v, obj : null }); + + variables.set("split", split); + variables.set("makeIterator", makeIterator); + + var c = Type.getClass(api); + for( f in (c == null ? Reflect.fields(api) : Type.getInstanceFields(c)) ) { + var fv = Reflect.field(api, f); + if( !Reflect.isFunction(fv) ) continue; + if( f.charCodeAt(0) == "_".code ) f = f.substr(1); + variables.set(f, fv); + // create the async wrapper if doesn't exists + if( f.substr(0, 2) != "a_" ) + funs.push({ v : f, obj : api }); + } + + for( v in funs ) { + if( variables.exists("a_" + v.v) ) continue; + var fv : Dynamic = variables.get(v.v); + var obj = v.obj; + variables.set("a_" + v.v, Reflect.makeVarArgs(function(args:Array) { + var onEnd = args.shift(); + onEnd(Reflect.callMethod(obj, fv, args)); + })); + } + } + + public function hasMethod( name : String ) { + var v = variables.get(name); + return v != null && Reflect.isFunction(v); + } + + public function callValue( value : Dynamic, args : Array, ?onResult : Dynamic -> Void, ?vthis : {} ) { + var oldThis = variables.get("this"); + if( vthis != null ) + variables.set("this", vthis); + if( onResult == null ) + onResult = function(_) {}; + args.unshift(onResult); + Reflect.callMethod(null, value, args); + variables.set("this", oldThis); + } + + public function callAsync( id : String, args, ?onResult, ?vthis : {} ) { + var v = variables.get(id); + if( v == null ) + throw "Missing function " + id + "()"; + callValue(v, args, onResult, vthis); + } + + function split( rest : Dynamic -> Void, args : Array ) { + if( args.length == 0 ) + rest(null); + else { + var count = args.length; + function next(_) { + if( --count == 0 ) rest(null); + } + for( a in args ) + a(next); + } + } + + override function fcall( o : Dynamic, f : String, args : Array ) : Dynamic { + var m = Reflect.field(o, f); + if( m == null ) { + if( f.substr(0, 2) == "a_" ) { + m = Reflect.field(o, f.substr(2)); + // fallback on sync version + if( m != null ) { + var onEnd = args.shift(); + onEnd(call(o, m, args)); + return null; + } + // fallback on generic script + m = Reflect.field(o, "scriptCall"); + if( m != null ) { + call(o, m, [args.shift(), f.substr(2), args]); + return null; + } + } else { + // fallback on generic script + m = Reflect.field(o, "scriptCall"); + if( m != null ) { + var result : Dynamic = null; + call(o, m, [function(r) result = r, f, args]); + return result; + } + } + error(ECustom(o + " has no method " + f)); + } + return call(o, m, args); + } + +} diff --git a/lib/hscript/hscript/Bytes.hx b/lib/hscript/hscript/Bytes.hx new file mode 100644 index 00000000..e4ede914 --- /dev/null +++ b/lib/hscript/hscript/Bytes.hx @@ -0,0 +1,518 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr; + +class Bytes { + + var bin : haxe.io.Bytes; + var bout : haxe.io.BytesBuffer; + var pin : Int; + var hstrings : Map; + var strings : Array; + var nstrings : Int; + + public var storeTypes : Bool = false; + + function new( ?bin ) { + this.bin = bin; + pin = 0; + bout = new haxe.io.BytesBuffer(); + hstrings = new Map(); + strings = [null]; + nstrings = 1; + } + + function doEncodeString( v : String ) { + var vid = hstrings.get(v); + if( vid == null ) { + if( nstrings == 256 ) { + hstrings = new Map(); + nstrings = 1; + } + hstrings.set(v,nstrings); + bout.addByte(0); + var vb = haxe.io.Bytes.ofString(v); + bout.addByte(vb.length); + bout.add(vb); + nstrings++; + } else + bout.addByte(vid); + } + + function doDecodeString() { + var id = bin.get(pin++); + if( id == 0 ) { + var len = bin.get(pin); + var str = #if (haxe_ver < 3.103) bin.readString(pin+1,len); #else bin.getString(pin+1,len); #end + pin += len + 1; + if( strings.length == 255 ) + strings = [null]; + strings.push(str); + return str; + } + return strings[id]; + } + + function doEncodeInt(v: Int) { + bout.addInt32(v); + } + + function doEncodeConst( c : Const ) { + switch( c ) { + case CInt(v): + if( v >= 0 && v <= 255 ) { + bout.addByte(0); + bout.addByte(v); + } else { + bout.addByte(1); + doEncodeInt(v); + } + case CFloat(f): + bout.addByte(2); + doEncodeString(Std.string(f)); + case CString(s): + bout.addByte(3); + doEncodeString(s); + } + } + + function doDecodeInt() { + var i = bin.getInt32(pin); + pin += 4; + return i; + } + + function doDecodeConst() { + return switch( bin.get(pin++) ) { + case 0: + CInt(bin.get(pin++)); + case 1: + var i = doDecodeInt(); + CInt(i); + case 2: + CFloat( Std.parseFloat(doDecodeString()) ); + case 3: + CString( doDecodeString() ); + default: + throw "Invalid code "+bin.get(pin-1); + } + } + + function doEncode( e : Expr ) { + #if hscriptPos + doEncodeString(e.origin); + doEncodeInt(e.line); + var e = e.e; + #end + bout.addByte(exprIndex(e)); + switch( e ) { + case EConst(c): + doEncodeConst(c); + case EIdent(v): + doEncodeString(v); + case EVar(n,t,e): + doEncodeString(n); + encodeType(t); + if( e == null ) + bout.addByte(255); + else + doEncode(e); + case EParent(e): + doEncode(e); + case EBlock(el): + bout.addByte(el.length); + for( e in el ) + doEncode(e); + case EField(e,f): + doEncode(e); + doEncodeString(f); + case EBinop(op,e1,e2): + doEncodeString(op); + doEncode(e1); + doEncode(e2); + case EUnop(op,prefix,e): + doEncodeString(op); + bout.addByte(prefix?1:0); + doEncode(e); + case ECall(e,el): + doEncode(e); + bout.addByte(el.length); + for( e in el ) + doEncode(e); + case EIf(cond,e1,e2): + doEncode(cond); + doEncode(e1); + if( e2 == null ) + bout.addByte(255); + else + doEncode(e2); + case EWhile(cond,e): + doEncode(cond); + doEncode(e); + case EDoWhile(cond,e): + doEncode(cond); + doEncode(e); + case EFor(v,it,e): + doEncodeString(v); + doEncode(it); + doEncode(e); + case EForGen(it,e): + doEncode(it); + doEncode(e); + case EBreak, EContinue: + case EFunction(params,e,name,t): + bout.addByte(params.length); + for( p in params ) + doEncodeString(p.name); + doEncode(e); + doEncodeString(name == null?"":name); + encodeType(t); + case EReturn(e): + if( e == null ) + bout.addByte(255); + else + doEncode(e); + case EArray(e,index): + doEncode(e); + doEncode(index); + case EArrayDecl(el): + if( el.length >= 255 ) throw "assert"; + bout.addByte(el.length); + for( e in el ) + doEncode(e); + case ENew(cl,params): + doEncodeString(cl); + bout.addByte(params.length); + for( e in params ) + doEncode(e); + case EThrow(e): + doEncode(e); + case ETry(e,v,t,ecatch): + doEncode(e); + doEncodeString(v); + encodeType(t); + doEncode(ecatch); + case EObject(fl): + bout.addByte(fl.length); + for( f in fl ) { + doEncodeString(f.name); + doEncode(f.e); + } + case ETernary(cond, e1, e2): + doEncode(cond); + doEncode(e1); + doEncode(e2); + case ESwitch(e, cases, def): + doEncode(e); + for( c in cases ) { + if( c.values.length == 0 ) throw "assert"; + for( v in c.values ) + doEncode(v); + bout.addByte(255); + doEncode(c.expr); + } + bout.addByte(255); + if( def == null ) bout.addByte(255) else doEncode(def); + case EMeta(name,args,e): + doEncodeString(name); + bout.addByte(args == null ? 0 : args.length + 1); + if( args != null ) for( e in args ) doEncode(e); + doEncode(e); + case ECheckType(e,t): + doEncode(e); + encodeType(t); + case ECast(e, t): + doEncode(e); + encodeType(t); + } + } + + function encodeType( t : CType ) { + if( !storeTypes ) + return; + if( t == null ) { + bout.addByte(255); + return; + } + bout.addByte(t.getIndex()); + switch( t ) { + case CTPath(path, params): + doEncodeString(path.join(".")); + if( params == null ) + bout.addByte(255); + else { + bout.addByte(params.length); + for( p in params ) + encodeType(p); + } + case CTFun(args, ret): + bout.addByte(args.length); + for( a in args ) + encodeType(a); + encodeType(ret); + case CTAnon(fields): + doEncodeInt(fields.length); + for( f in fields ) { + doEncodeString(f.name); + encodeType(f.t); + if( f.meta == null ) + bout.addByte(255); + else { + bout.addByte(f.meta.length); + for( m in f.meta ) { + doEncodeString(m.name); + doEncodeInt(m.params.length); + for( p in m.params ) + doEncode(p); + } + } + } + case CTParent(t): + encodeType(t); + case CTOpt(t): + encodeType(t); + case CTNamed(n, t): + doEncodeString(n); + encodeType(t); + case CTExpr(e): + doEncode(e); + } + } + + function exprIndex(e):Int { + return switch (e) { + case EConst(_): 0; + case EIdent(_): 1; + case EVar(_): 2; + case EParent(_): 3; + case EBlock(_): 4; + case EField(_): 5; + case EBinop(_): 6; + case EUnop(_): 7; + case ECall(_): 8; + case EIf(_): 9; + case EWhile(_): 10; + case EFor(_): 11; + case EBreak: 12; + case EContinue: 13; + case EFunction(_): 14; + case EReturn(_): 15; + case EArray(_): 16; + case EArrayDecl(_): 17; + case ENew(_): 18; + case EThrow(_): 19; + case ETry(_): 20; + case EObject(_): 21; + case ETernary(_): 22; + case ESwitch(_): 23; + case EDoWhile(_): 24; + case EMeta(_): 25; + case ECheckType(_): 26; + case EForGen(_): 27; + case ECast(_): 28; + } + } + + function doDecode() : Expr { + #if hscriptPos + if( getByte() == 255 ) + return null; + pin--; + var origin = doDecodeString(); + var line = doDecodeInt(); + return { e : _doDecode(), pmin : 0, pmax : 0, origin : origin, line : line }; + } + function _doDecode() : ExprDef { + #end + return switch( getByte() ) { + case 0: + EConst( doDecodeConst() ); + case 1: + EIdent( doDecodeString() ); + case 2: + var v = doDecodeString(); + EVar(v,decodeType(),doDecode()); + case 3: + EParent(doDecode()); + case 4: + var a = new Array(); + for( i in 0...getByte() ) + a.push(doDecode()); + EBlock(a); + case 5: + var e = doDecode(); + EField(e,doDecodeString()); + case 6: + var op = doDecodeString(); + var e1 = doDecode(); + EBinop(op,e1,doDecode()); + case 7: + var op = doDecodeString(); + var prefix = getByte() != 0; + EUnop(op,prefix,doDecode()); + case 8: + var e = doDecode(); + var params = new Array(); + for( i in 0...getByte() ) + params.push(doDecode()); + ECall(e,params); + case 9: + var cond = doDecode(); + var e1 = doDecode(); + EIf(cond,e1,doDecode()); + case 10: + var cond = doDecode(); + EWhile(cond,doDecode()); + case 11: + var v = doDecodeString(); + var it = doDecode(); + EFor(v,it,doDecode()); + case 12: + EBreak; + case 13: + EContinue; + case 14: + var params = new Array(); + for( i in 0...getByte() ) + params.push({ name : doDecodeString() }); + var e = doDecode(); + var name = doDecodeString(); + EFunction(params,e,(name == "") ? null: name,decodeType()); + case 15: + EReturn(doDecode()); + case 16: + var e = doDecode(); + EArray(e,doDecode()); + case 17: + var el = new Array(); + for( i in 0...getByte() ) + el.push(doDecode()); + EArrayDecl(el); + case 18: + var cl = doDecodeString(); + var el = new Array(); + for( i in 0...getByte() ) + el.push(doDecode()); + ENew(cl,el); + case 19: + EThrow(doDecode()); + case 20: + var e = doDecode(); + var v = doDecodeString(); + ETry(e,v,decodeType(),doDecode()); + case 21: + var fl = new Array(); + for( i in 0...getByte() ) { + var name = doDecodeString(); + var e = doDecode(); + fl.push({ name : name, e : e }); + } + EObject(fl); + case 22: + var cond = doDecode(); + var e1 = doDecode(); + var e2 = doDecode(); + ETernary(cond, e1, e2); + case 23: + var e = doDecode(); + var cases = []; + while( true ) { + var v = doDecode(); + if( v == null ) break; + var values = [v]; + while( true ) { + v = doDecode(); + if( v == null ) break; + values.push(v); + } + cases.push( { values : values, expr : doDecode() } ); + } + var def = doDecode(); + ESwitch(e, cases, def); + case 24: + var cond = doDecode(); + EDoWhile(cond,doDecode()); + case 25: + var name = doDecodeString(); + var count = getByte(); + var args = count == 0 ? null : [for( i in 0...count - 1 ) doDecode()]; + EMeta(name, args, doDecode()); + case 26: + ECheckType(doDecode(), decodeType()); + case 27: + EForGen(doDecode(), doDecode()); + case 28: + ECast(doDecode(), decodeType()); + case 255: + null; + default: + throw "Invalid code "+bin.get(pin - 1); + } + } + + inline function getByte() { + return bin.get(pin++); + } + + function decodeType() : CType { + if( !storeTypes ) + return null; + return switch( getByte() ) { + case 255: null; + case 0: + var path = doDecodeString().split("."); + var plen = getByte(); + var params = plen == 255 ? null : [for( i in 0...plen ) decodeType()]; + CTPath(path, params); + case 1: + CTFun([for( v in 0...getByte() ) decodeType()], decodeType()); + case 2: + CTAnon([for( v in 0...doDecodeInt() ) { name : doDecodeString(), t : decodeType(), meta : { + var v = getByte(); + if( v == 255 ) null else [for( i in 0...v ) { name : doDecodeString(), params : [for( i in 0...getByte() ) doDecode()] }]; + }}]); + case 3: + CTParent(decodeType()); + case 4: + CTOpt(decodeType()); + case 5: + CTNamed(doDecodeString(),decodeType()); + case 6: + CTExpr(doDecode()); + default: + throw "Invalid code "+bin.get(pin - 1); + } + } + + public static function encode( e : Expr ) : haxe.io.Bytes { + var b = new Bytes(); + b.doEncode(e); + return b.bout.getBytes(); + } + + public static function decode( bytes : haxe.io.Bytes ) : Expr { + var b = new Bytes(bytes); + return b.doDecode(); + } + +} diff --git a/lib/hscript/hscript/Checker.hx b/lib/hscript/hscript/Checker.hx new file mode 100644 index 00000000..1cacd623 --- /dev/null +++ b/lib/hscript/hscript/Checker.hx @@ -0,0 +1,1618 @@ +package hscript; +import hscript.Expr; + +/** + This is a special type that can be used in API. + It will be type-checked as `Script` but will compile/execute as `Real` +**/ +typedef TypeCheck = Real; + +enum TType { + TMono( r : { r : TType } ); + TVoid; + TInt; + TFloat; + TBool; + TDynamic; + TParam( name : String ); + TUnresolved( name : String ); + TNull( t : TType ); + TInst( c : CClass, args : Array ); + TEnum( e : CEnum, args : Array ); + TType( t : CTypedef, args : Array ); + TAbstract( a : CAbstract, args : Array ); + TFun( args : Array<{ name : String, opt : Bool, t : TType }>, ret : TType ); + TAnon( fields : Array<{ name : String, opt : Bool, t : TType }> ); + TLazy( f : Void -> TType ); +} + +private enum WithType { + NoValue; + Value; + WithType( t : TType ); +} + +enum CTypedecl { + CTClass( c : CClass ); + CTEnum( e : CEnum ); + CTTypedef( t : CTypedef ); + CTAlias( t : TType ); + CTAbstract( a : CAbstract ); +} + +typedef CMetadata = Array<{ name : String, params : Null> }>; + +typedef CNamedType = { + var name : String; + var params : Array; + var ?meta : CMetadata; +} + +typedef CClass = {> CNamedType, + var ?superClass : TType; + var ?constructor : CField; + var ?interfaces : Array; + var ?isInterface : Bool; + var fields : Map; + var statics : Map; +} + +typedef CField = { + var isPublic : Bool; + var canWrite : Bool; + var complete : Bool; + var params : Array; + var name : String; + var t : TType; + var ?meta : CMetadata; +} + +typedef CEnum = {> CNamedType, + var constructors : Array<{ name : String, ?args : Array<{ name : String, opt : Bool, t : TType }> }>; +} + +typedef CTypedef = {> CNamedType, + var t : TType; +} + +typedef CAbstract = {> CNamedType, + var t : TType; + var from : Array; + var to : Array; + var forwards : Map; + var impl : CClass; +} + +class Completion { + public var expr : Expr; + public var t : TType; + public function new(expr,t) { + this.expr = expr; + this.t = t; + } +} + +@:allow(hscript.Checker) +class CheckerTypes { + + var types : Map = new Map(); + var t_string : TType; + var localParams : Map; + var parser : hscript.Parser; + + public function new() { + types = new Map(); + types.set("Void",CTAlias(TVoid)); + types.set("Int",CTAlias(TInt)); + types.set("Float",CTAlias(TFloat)); + types.set("Bool",CTAlias(TBool)); + types.set("Dynamic",CTAlias(TDynamic)); + parser = new hscript.Parser(); + } + + public function addXmlApi( api : Xml ) { + var types = new haxe.rtti.XmlParser(); + types.process(api, ""); + var todo = []; + for( v in types.root ) + addXmlType(v,todo); + for( f in todo ) + f(); + t_string = getType("String"); + } + + public function defineClass( name : String, ?ct : CClass ) { + if( ct == null ) + ct = { + name : name, + fields : [], + statics : [], + params : [], + }; + types.set(name, CTClass(ct)); + return ct; + } + + function addXmlType(x:haxe.rtti.CType.TypeTree,todo:ArrayVoid>) { + switch (x) { + case TPackage(name, full, subs): + for( s in subs ) addXmlType(s,todo); + case TClassdecl(c): + if( types.exists(c.path) ) return; + var cl : CClass = { + name : c.path, + params : [], + fields : new Map(), + statics : new Map(), + }; + addMeta(c,cl); + if( c.isInterface ) + cl.isInterface = true; + for( p in c.params ) + cl.params.push(TParam(p)); + todo.push(function() { + localParams = [for( t in cl.params ) c.path+"."+Checker.typeStr(t) => t]; + if( c.superClass != null ) + cl.superClass = getType(c.superClass.path, [for( t in c.superClass.params ) makeXmlType(t)]); + if( c.interfaces != null ) { + cl.interfaces = []; + for( i in c.interfaces ) + cl.interfaces.push(getType(i.path, [for( t in i.params ) makeXmlType(t)])); + } + var pkeys = []; + function initField(f:haxe.rtti.CType.ClassField, fields) { + if( f.isOverride || f.name.substr(0,4) == "get_" || f.name.substr(0,4) == "set_" ) return; + var complete = !StringTools.startsWith(f.name,"__"); // __uid, etc. (no metadata in such fields) + for( m in f.meta ) { + if( m.name == ":noScript" ) + return; + if( m.name == ":noCompletion" ) + complete = false; + } + var fl : CField = { isPublic : f.isPublic, canWrite : f.set.match(RNormal | RCall(_) | RDynamic), complete : complete, params : [], name : f.name, t : null }; + for( p in f.params ) { + var pt = TParam(p); + var key = f.name+"."+p; + pkeys.push(key); + fl.params.push(pt); + localParams.set(key, pt); + } + fl.t = makeXmlType(f.type); + if( f.meta != null && f.meta.length > 0 ) { + fl.meta = []; + for( m in f.meta ) + fl.meta.push({ name : m.name, params : [for( p in m.params ) try parser.parseString(p) catch( e : hscript.Expr.Error ) null] }); + } + while( pkeys.length > 0 ) + localParams.remove(pkeys.pop()); + if( fl.name == "new" ) + cl.constructor = fl; + else + fields.set(f.name, fl); + } + for( f in c.fields ) + initField(f, cl.fields); + for( f in c.statics ) + initField(f, cl.statics); + localParams = null; + }); + types.set(cl.name, CTClass(cl)); + case TEnumdecl(e): + if( types.exists(e.path) ) return; + var en : CEnum = { + name : e.path, + params : [], + constructors: [], + }; + addMeta(e,en); + for( p in e.params ) + en.params.push(TParam(p)); + todo.push(function() { + localParams = [for( t in en.params ) e.path+"."+Checker.typeStr(t) => t]; + for( c in e.constructors ) + en.constructors.push({ name : c.name, args : c.args == null ? null : [for( a in c.args ) { name : a.name, opt : a.opt, t : makeXmlType(a.t) }] }); + localParams = null; + }); + types.set(en.name, CTEnum(en)); + case TTypedecl(t): + if( types.exists(t.path) ) return; + var td : CTypedef = { + name : t.path, + params : [], + t : null, + }; + for( p in t.params ) + td.params.push(TParam(p)); + if( t.path == "hscript.TypeCheck" ) + td.params.reverse(); + todo.push(function() { + localParams = [for( pt in td.params ) t.path+"."+Checker.typeStr(pt) => pt]; + td.t = makeXmlType(t.type); + localParams = null; + }); + types.set(t.path, CTTypedef(td)); + case TAbstractdecl(a): + if( types.exists(a.path) ) return; + var ta : CAbstract = { + name : a.path, + params : [], + t : null, + from : [], + to : [], + forwards : new Map(), + impl : null, + }; + addMeta(a,ta); + for( p in a.params ) + ta.params.push(TParam(p)); + todo.push(function() { + localParams = [for( t in ta.params ) a.path+"."+Checker.typeStr(t) => t]; + ta.t = makeXmlType(a.athis); + for( f in a.from ) + if( f.field == null ) + ta.from.push(makeXmlType(f.t)); + for( t in a.to ) + if( t.field == null ) + ta.to.push(makeXmlType(t.t)); + for( m in a.meta ) + if( m.name == ":forward" && m.params != null ) { + for( i in m.params ) + ta.forwards.set(i, true); + } + if( a.impl != null ) { + var t = resolve(a.impl.path); + if( t != null ) + switch( t ) { + case TInst(c,_): ta.impl = c; + default: + } + } + localParams = null; + }); + types.set(a.path, CTAbstract(ta)); + } + } + + function addMeta( src : haxe.rtti.CType.TypeInfos, to : CNamedType ) { + if( src.meta == null || src.meta.length == 0 ) + return; + to.meta = []; + for( m in src.meta ) + to.meta.push({ name : m.name, params : [for( p in m.params ) try parser.parseString(p) catch( e : hscript.Expr.Error ) null] }); + } + + function makeXmlType( t : haxe.rtti.CType.CType ) : TType { + return switch (t) { + case CUnknown: TUnresolved("Unknown"); + case CEnum(name, params): getType(name,[for( t in params ) makeXmlType(t)]); + case CClass(name, params): getType(name,[for( t in params ) makeXmlType(t)]); + case CTypedef(name, params): getType(name,[for( t in params ) makeXmlType(t)]); + case CFunction(args, ret): TFun([for( a in args ) { name : a.name, opt : a.opt, t : makeXmlType(a.t) }], makeXmlType(ret)); + case CAnonymous(fields): + inline function isOpt(m:haxe.rtti.CType.MetaData) { + if( m == null ) return false; + var b = false; + for( m in m ) if( m.name == ":optional" ) { b = true; break; } + return b; + } + TAnon([for( f in fields ) { name : f.name, t : makeXmlType(f.type), opt : isOpt(f.meta) }]); + case CDynamic(t): TDynamic; + case CAbstract(name, params): + switch( name ) { + default: + getType(name,[for( t in params ) makeXmlType(t)]); + } + } + } + + function getType( name : String, ?args : Array ) : TType { + if( localParams != null ) { + var t = localParams.get(name); + if( t != null ) return t; + } + var t = resolve(name,args); + if( t == null ) { + var pack = name.split("."); + if( pack.length > 1 ) { + // bugfix for some args reported as pack._Name.Name while they are not private + var priv = pack[pack.length-2]; + if( priv.charCodeAt(0) == "_".code ) { + pack.remove(priv); + return getType(pack.join("."), args); + } + } + return TUnresolved(name); // most likely private class + } + return t; + } + + public function resolve( name : String, ?args : Array ) : TType { + if( name == "Null" ) { + if( args == null || args.length != 1 ) throw "Missing Null parameter"; + return TNull(args[0]); + } + var t = types.get(name); + if( t == null ) return null; + if( args == null ) args = []; + return switch( t ) { + case CTClass(c): TInst(c,args); + case CTEnum(e): TEnum(e,args); + case CTTypedef(t): TType(t,args); + case CTAbstract(a): TAbstract(a, args); + case CTAlias(t): t; + } + } + +} + +class Checker { + + public var types : CheckerTypes; + var locals : Map; + var globals : Map = new Map(); + var events : Map = new Map(); + var currentFunType : TType; + var isCompletion : Bool; + var allowDefine : Bool; + var hasReturn : Bool; + public var checkPrivate : Bool = true; + public var allowAsync : Bool; + public var allowReturn : Null; + public var allowGlobalsDefine : Bool; + public var allowUntypedMeta : Bool; + + public function new( ?types ) { + if( types == null ) types = new CheckerTypes(); + this.types = types; + } + + public function setGlobals( cl : CClass, ?params : Array, allowPrivate = false ) { + if( params == null ) + params = [for( p in cl.params ) makeMono()]; + while( true ) { + for( f in cl.fields ) + if( f.isPublic || allowPrivate ) + setGlobal(f.name, f.params.length == 0 ? f.t : TLazy(function() { + var t = apply(f.t,f.params,[for( i in 0...f.params.length) makeMono()]); + return apply(t, cl.params, params); + })); + if( cl.superClass == null ) + break; + cl = switch( cl.superClass ) { + case TInst(csup,pl): + params = [for( p in pl ) apply(p,cl.params,params)]; + csup; + default: throw "assert"; + } + } + } + + public function removeGlobal( name : String ) { + globals.remove(name); + } + + public function setGlobal( name : String, type : TType ) { + globals.set(name, type); + } + + public function setEvent( name : String, type : TType ) { + events.set(name, type); + } + + public function getGlobals() { + return globals; + } + + public dynamic function onTopDownEnum( en : CEnum, field : String ) { + return false; + } + + function typeArgs( args : Array, pos : Expr ) { + return [for( i in 0...args.length ) { + var a = args[i]; + var at = a.t == null ? makeMono() : makeType(a.t, pos); + { name : a.name, opt : a.opt, t : at }; + }]; + } + + public function check( expr : Expr, ?withType : WithType, ?isCompletion = false ) { + if( withType == null ) withType = NoValue; + locals = new Map(); + if( types.t_string == null ) + types.t_string = types.getType("String"); + allowDefine = allowGlobalsDefine; + this.isCompletion = isCompletion; + if( edef(expr).match(EFunction(_)) ) + expr = mk(EBlock([expr]), expr); // single function might be self recursive + switch( edef(expr) ) { + case EBlock(el): + var delayed = []; + var last = TVoid; + for( e in el ) { + while( true ) { + switch( edef(e) ) { + case EMeta(_,_,e2): e = e2; + default: break; + } + } + switch( edef(e) ) { + case EFunction(args,_,name,ret) if( name != null ): + var tret = ret == null ? makeMono() : makeType(ret, e); + var ft = TFun(typeArgs(args,e),tret); + locals.set(name, ft); + delayed.push(function() { + currentFunType = ft; + typeExpr(e, NoValue); + return ft; + }); + default: + for( f in delayed ) f(); + delayed = []; + if( el[el.length-1] == e ) + last = typeExpr(e, withType); + else + typeExpr(e, NoValue); + } + } + for( f in delayed ) + last = f(); + return last; + default: + } + return typeExpr(expr,withType); + } + + inline function edef( e : Expr ) { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + function punion( e1 : Expr, e2 : Expr ) : Expr { + #if hscriptPos + return { + pmin : e1.pmin < e2.pmin ? e1.pmin : e2.pmin, + pmax : e1.pmax > e2.pmax ? e1.pmax : e2.pmax, + origin : e1.origin, + line : e1.line < e2.line ? e1.line : e2.line, + e : null, + }; + #else + return e1; + #end + } + + inline function error( msg : String, curExpr : Expr ) { + var e = ECustom(msg); + #if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end + if( !isCompletion ) throw e; + } + + function saveLocals() { + return [for( k in locals.keys() ) k => locals.get(k)]; + } + + function makeType( t : CType, e : Expr ) : TType { + return switch (t) { + case CTPath(path, params): + var params = params == null ? [] : [for( p in params ) makeType(p,e)]; + var ct = types.resolve(path.join("."),params); + if( ct == null ) { + // maybe a subtype that is public ? + var pack = path.copy(); + var name = pack.pop(); + if( pack.length > 0 && pack[pack.length-1].charCodeAt(0) >= 'A'.code && pack[pack.length-1].charCodeAt(0) <= 'Z'.code ) { + pack.pop(); + pack.push(name); + ct = types.resolve(pack.join("."), params); + } + } + if( ct == null ) { + error("Unknown type "+path, e); + ct = TDynamic; + } + return ct; + case CTFun(args, ret): + var i = 0; + return TFun([for( a in args ) { name : "p"+(i++), opt : false, t : makeType(a,e) }], makeType(ret,e)); + case CTAnon(fields): + return TAnon([for( f in fields ) { name : f.name, opt : false, t : makeType(f.t,e) }]); + case CTParent(t): + return makeType(t,e); + case CTNamed(n, t): + return makeType(t,e); + case CTOpt(t): + return makeType(t,e); + case CTExpr(_): + error("Unsupported expr type parameter", e); + return null; + } + } + + public static function typeStr( t : TType ) { + inline function makeArgs(args:Array) return args.length==0 ? "" : "<"+[for( t in args ) typeStr(t)].join(",")+">"; + return switch (t) { + case TMono(r): r.r == null ? "Unknown" : typeStr(r.r); + case TInst(c, args): c.name + makeArgs(args); + case TEnum(e, args): e.name + makeArgs(args); + case TType(t, args): + if( t.name == "hscript.TypeCheck" ) + typeStr(args[1]); + else + t.name + makeArgs(args); + case TAbstract(a, args): a.name + makeArgs(args); + case TFun(args, ret): "(" + [for( a in args ) (a.opt?"?":"")+(a.name == "" ? "" : a.name+":")+typeStr(a.t)].join(", ")+") -> "+typeStr(ret); + case TAnon(fields): "{" + [for( f in fields ) (f.opt?"?":"")+f.name+":"+typeStr(f.t)].join(", ")+"}"; + case TParam(name): name; + case TNull(t): "Null<"+typeStr(t)+">"; + case TUnresolved(name): "?"+name; + default: t.getName().substr(1); + } + } + + public static function typeIter( t : TType, callb : TType -> Void ) { + switch( t ) { + case TMono(r) if( r.r != null ): callb(r.r); + case TNull(t): callb(t); + case TInst(_,tl), TAbstract(_,tl), TEnum(_,tl), TType(_,tl): + for( t in tl ) callb(t); + case TFun(args,ret): + for( t in args ) callb(t.t); + callb(ret); + case TAnon(fl): + for( f in fl ) + callb(f.t); + case TLazy(f): + callb(f()); + default: + } + } + + function linkLoop( a : TType, t : TType ) { + if( t == a ) return true; + switch( t ) { + case TMono(r): + if( r.r == null ) return false; + return linkLoop(a,r.r); + case TEnum(_,tl), TInst(_,tl), TType(_,tl), TAbstract(_,tl): + for( t in tl ) + if( linkLoop(a,t) ) + return true; + return false; + case TFun(args,ret): + for( arg in args ) + if( linkLoop(a,arg.t) ) + return true; + return linkLoop(a,ret); + case TDynamic: + if( t == TDynamic ) + return false; + return linkLoop(a,TDynamic); + case TAnon(fl): + for( f in fl ) + if( linkLoop(a, f.t) ) + return true; + return false; + default: + return false; + } + } + + function link( a : TType, b : TType, r : { r : TType } ) { + if( linkLoop(a,b) ) + return follow(b) == a; + if( b == TDynamic ) + return true; + r.r = b; + return true; + } + + function typeEq( t1 : TType, t2 : TType ) { + if( t1 == t2 ) + return true; + switch( [t1,t2] ) { + case [TMono(r), _]: + if( r.r == null ) { + if( !link(t1,t2,r) ) + return false; + r.r = t2; + return true; + } + return typeEq(r.r, t2); + case [_, TMono(r)]: + if( r.r == null ) { + if( !link(t2,t1,r) ) + return false; + r.r = t1; + return true; + } + return typeEq(t1, r.r); + case [TType(t1,pl1),TType(t2,pl2)] if( t1 == t2 ): + for( i in 0...pl1.length ) + if( !typeEq(pl1[i],pl2[i]) ) + return false; + return true; + case [TType(t1,pl1), _]: + return typeEq(apply(t1.t, t1.params, pl1), t2); + case [_,TType(t2,pl2)]: + return typeEq(t1, apply(t2.t, t2.params, pl2)); + case [TInst(cl1,pl1), TInst(cl2,pl2)] if( cl1 == cl2 ): + for( i in 0...pl1.length ) + if( !typeEq(pl1[i],pl2[i]) ) + return false; + return true; + case [TEnum(e1,pl1), TEnum(e2,pl2)] if( e1 == e2 ): + for( i in 0...pl1.length ) + if( !typeEq(pl1[i],pl2[i]) ) + return false; + return true; + case [TAbstract(a1,pl1), TAbstract(a2,pl2)] if( a1 == a2 ): + for( i in 0...pl1.length ) + if( !typeEq(pl1[i],pl2[i]) ) + return false; + return true; + case [TNull(t1), TNull(t2)]: + return typeEq(t1,t2); + case [TNull(t1), _]: + return typeEq(t1,t2); + case [_, TNull(t2)]: + return typeEq(t1,t2); + case [TFun(args1,r1), TFun(args2,r2)] if( args1.length == args2.length ): + for( i in 0...args1.length ) + if( !typeEq(args1[i].t, args2[i].t) ) + return false; + return typeEq(r1, r2); + case [TAnon(a1),TAnon(a2)] if( a1.length == a2.length ): + var m = new Map(); + for( f in a2 ) + m.set(f.name, f); + for( f1 in a1 ) { + var f2 = m.get(f1.name); + if( f2 == null ) return false; + if( !typeEq(f1.t,f2.t) ) + return false; + } + return true; + default: + } + return false; + } + + public function tryUnify( t1 : TType, t2 : TType ) { + if( t1 == t2 ) + return true; + switch( [t1,t2] ) { + case [TMono(r), _]: + if( r.r == null ) { + if( !link(t1,t2,r) ) + return false; + r.r = t2; + return true; + } + return tryUnify(r.r, t2); + case [_, TMono(r)]: + if( r.r == null ) { + if( !link(t2,t1,r) ) + return false; + r.r = t1; + return true; + } + return tryUnify(t1, r.r); + case [TType(t1,pl1), _]: + return tryUnify(apply(t1.t, t1.params, pl1), t2); + case [_,TType(t2,pl2)]: + return tryUnify(t1, apply(t2.t, t2.params, pl2)); + case [TNull(t1), _]: + return tryUnify(t1,t2); + case [_, TNull(t2)]: + return tryUnify(t1,t2); + case [TFun(args1,r1),TFun(args2,r2)] if( args1.length == args2.length ): + for( i in 0...args1.length ) { + var a1 = args1[i]; + var a2 = args2[i]; + if( a2.opt && !a1.opt ) return false; + if( !tryUnify(a2.t, a1.t) ) return false; + } + if( follow(r2) == TVoid ) + return true; + return tryUnify(r1,r2); + case [_, TDynamic]: + return true; + case [TDynamic, _]: + return true; + case [TAnon(a1),TAnon(a2)]: + if( a2.length == 0 ) // always unify with {} + return true; + var m = new Map(); + for( f in a1 ) + m.set(f.name, f); + for( f2 in a2 ) { + var f1 = m.get(f2.name); + if( f1 == null ) { + if( f2.opt ) continue; + return false; + } + if( !typeEq(f1.t,f2.t) ) + return false; + } + return true; + case [TInst(cl1,pl1), TInst(cl2,pl2)]: + while( cl1 != cl2 ) { + if( cl1.interfaces != null ) { + for( i in cl1.interfaces ) { + switch( i ) { + case TInst(cli, args): + var i = TInst(cli, [for( a in args ) apply(a, cl1.params, pl1)]); + if( tryUnify(i, t2) ) + return true; + default: + throw "assert"; + } + } + } + switch( cl1.superClass ) { + case null: return false; + case TInst(c, args): + pl1 = [for( a in args ) apply(a,cl1.params,pl1)]; + cl1 = c; + default: throw "assert"; + } + } + for( i in 0...pl1.length ) + if( !typeEq(pl1[i],pl2[i]) ) + return false; + return true; + case [TInst(cl1,pl1),TAnon(fl)]: + for( i in 0...fl.length ) { + var f2 = fl[i]; + var f1 = null; + var cl = cl1; + while( true ) { + f1 = cl.fields.get(f2.name); + if( f1 != null ) break; + if( cl.superClass == null ) + return false; + cl = switch( cl.superClass ) { + case TInst(c,_): c; + default: throw "assert"; + } + } + if( !typeEq(apply(f1.t,cl1.params,pl1),f2.t) ) + return false; + } + return true; + case [TInt, TFloat]: + return true; + case [TFun(_), TAbstract({ name : "haxe.Function" },_)]: + return true; + case [_, TAbstract(a, args)]: + for( ft in a.from ) { + var t = apply(ft,a.params,args); + if( tryUnify(t1,t) ) + return true; + } + case [TAbstract(a, args), _]: + for( tt in a.to ) { + var t = apply(tt,a.params,args); + if( tryUnify(t,t2) ) + return true; + } + default: + } + return typeEq(t1,t2); + } + + public function unify( t1 : TType, t2 : TType, e : Expr ) { + if( !tryUnify(t1,t2) ) + error(typeStr(t1)+" should be "+typeStr(t2),e); + } + + public function apply( t : TType, params : Array, args : Array ) { + if( args.length != params.length ) throw "Invalid number of type parameters"; + if( args.length == 0 ) + return t; + var subst = new Map(); + for( i in 0...params.length ) + subst.set(params[i], args[i]); + function map(t:TType) { + var st = subst.get(t); + if( st != null ) return st; + return mapType(t,map); + } + return map(t); + } + + public function mapType( t : TType, f : TType -> TType ) { + switch (t) { + case TMono(r): + if( r.r == null ) return t; + return f(t); + case TVoid, TInt, TFloat,TBool,TDynamic,TParam(_), TUnresolved(_): + return t; + case TEnum(_,[]), TInst(_,[]), TAbstract(_,[]), TType(_,[]): + return t; + case TNull(t): + return TNull(f(t)); + case TInst(c, args): + return TInst(c, [for( t in args ) f(t)]); + case TEnum(e, args): + return TEnum(e, [for( t in args ) f(t)]); + case TType(t, args): + return TType(t, [for( t in args ) f(t)]); + case TAbstract(a, args): + return TAbstract(a, [for( t in args ) f(t)]); + case TFun(args, ret): + return TFun([for( a in args ) { name : a.name, opt : a.opt, t : f(a.t) }], f(ret)); + case TAnon(fields): + return TAnon([for( af in fields ) { name : af.name, opt : af.opt, t : f(af.t) }]); + case TLazy(l): + return f(l()); + } + } + + public function follow( t : TType ) { + return switch( t ) { + case TMono(r): if( r.r != null ) follow(r.r) else t; + case TType(t,args): follow(apply(t.t, t.params, args)); + case TNull(t): follow(t); + case TLazy(f): follow(f()); + default: t; + } + } + + public function getFields( t : TType ) : Array<{ name : String, t : TType }> { + var fields = []; + switch( follow(t) ) { + case TInst(c, args): + var map = (t) -> apply(t,c.params,args); + while( c != null ) { + for( fname in c.fields.keys() ) { + var f = c.fields.get(fname); + if( !f.isPublic || !f.complete ) continue; + var name = f.name, t = map(f.t); + if( allowAsync && StringTools.startsWith(name,"a_") ) { + t = unasync(t); + name = name.substr(2); + } + fields.push({ name : name, t : t }); + } + if( c.isInterface && c.interfaces != null ) { + for( i in c.interfaces ) { + for( f in getFields(i) ) + fields.push({ name : f.name, t : map(f.t) }); + } + } + if( c.superClass == null ) break; + switch( c.superClass ) { + case TInst(csup,args): + var curMap = map; + map = (t) -> curMap(apply(t,csup.params,args)); + c = csup; + default: + break; + } + } + case TAnon(fl): + for( f in fl ) + fields.push({ name : f.name, t : f.t }); + case TFun(args, ret): + if( isCompletion ) + fields.push({ name : "bind", t : TFun(args,TVoid) }); + case TAbstract(a,pl): + for( v in a.forwards.keys() ) { + var t = getField(apply(a.t, a.params, pl), v, null, false); + fields.push({ name : v, t : t}); + } + default: + } + return fields; + } + + function getField( t : TType, f : String, e : Expr, forWrite = false ) { + switch( follow(t) ) { + case TInst(c, args): + var cf = c.fields.get(f); + if( cf == null && allowAsync ) { + cf = c.fields.get("a_"+f); + if( cf != null ) { + var isPublic = true; // consider a_ prefixed as script specific + cf = { isPublic : isPublic, canWrite : false, params : cf.params, name : cf.name, t : unasync(cf.t), complete : cf.complete }; + if( cf.t == null ) cf = null; + } + } + if( cf == null && c.isInterface && c.interfaces != null ) { + for( i in c.interfaces ) { + var ft = getField(i, f, e, forWrite); + if( ft != null ) + return apply(ft, c.params, args); + } + } + if( cf == null ) { + if( c.superClass == null ) return null; + var ft = getField(c.superClass, f, e, forWrite); + if( ft != null ) ft = apply(ft, c.params, args); + return ft; + } + if( !cf.isPublic && checkPrivate ) + error("Can't access private field "+f+" on "+c.name, e); + if( forWrite && !cf.canWrite ) + error("Can't write readonly field "+f+" on "+c.name, e); + var t = cf.t; + if( cf.params != null ) t = apply(t, cf.params, [for( i in 0...cf.params.length ) makeMono()]); + return apply(t, c.params, args); + case TDynamic: + return makeMono(); + case TAnon(fields): + for( af in fields ) + if( af.name == f ) + return af.t; + return null; + case TAbstract(a,pl) if( a.forwards.exists(f) ): + return getField(apply(a.t, a.params, pl), f, e, forWrite); + default: + return null; + } + } + + public function unasync( t : TType ) : TType { + switch( follow(t) ) { + case TFun(args, ret) if( args.length > 0 ): + var rargs = args.copy(); + switch( follow(rargs.shift().t) ) { + case TFun([r],_): return TFun(rargs,r.t); + default: + } + default: + } + return null; + } + + function typeExprWith( expr : Expr, t : TType ) { + var et = typeExpr(expr, WithType(t)); + unify(et, t, expr); + return t; + } + + function makeMono() { + return TMono({r:null}); + } + + function makeIterator(t) : TType { + return TAnon([{ name : "next", opt : false, t : TFun([],t) }, { name : "hasNext", opt : false, t : TFun([],TBool) }]); + } + + function mk(e,p) : Expr { + #if hscriptPos + return { e : e, pmin : p.pmin, pmax : p.pmax, origin : p.origin, line : p.line }; + #else + return e; + #end + } + + function isString( t : TType ) { + t = follow(t); + return t.match(TInst({name:"String"},_)); + } + + function onCompletion( expr : Expr, t : TType ) { + if( isCompletion ) throw new Completion(expr, t); + } + + function typeField( o : Expr, f : String, expr : Expr, withType, forWrite : Bool ) { + if( f == null && isCompletion ) { + var ot = typeExpr(o, Value); + onCompletion(expr,ot); + return TDynamic; + } + var path = [{ f : f, e : expr }]; + while( true ) { + switch( edef(o) ) { + case EField(e,f) if( f != null ): + path.unshift({ f : f, e : o }); + o = e; + case EIdent(i): + path.unshift({ f : i, e : o }); + return typePath(path, withType, forWrite); + default: + break; + } + } + return readPath(typeExpr(o, Value), path, forWrite); + } + + function readPath( ot : TType, path : Array<{ f : String, e : Expr }>, forWrite ) { + for( p in path ) { + var ft = getField(ot, p.f, p.e, p == path[path.length-1] ? forWrite : false); + if( ft == null ) { + error(typeStr(ot)+" has no field "+p.f, p.e); + return TDynamic; + } + ot = ft; + } + return ot; + } + + function typePath( path : Array<{ f : String, e : Expr }>, withType, forWrite : Bool ) { + var root = path[0]; + var l = locals.get(root.f); + if( l != null ) { + path.shift(); + return readPath(l,path,forWrite); + } + var t = resolveGlobal(root.f,root.e,path.length == 1 ? withType : Value); + if( t != null ) { + path.shift(); + return readPath(t,path,forWrite); + } + var fields = []; + while( path.length > 1 ) { + var name = [for( p in path ) p.f].join("."); + var union = punion(path[0].e,path[path.length-1].e); + var t = resolveGlobal(name, union, Value); + if( t != null ) { + #if hscriptPos + if( union.e != null ) path[path.length-1].e.e = union.e; + #end + return readPath(t,fields,forWrite); + } + fields.unshift(path.pop()); + } + if( !isCompletion ) + error("Unknown identifier "+root.f, root.e); + return TDynamic; + } + + function hasMeta( meta : Metadata, name : String ) { + for( m in meta ) + if( m.name == name ) + return true; + return false; + } + + function resolveGlobal( name : String, expr : Expr, withType : WithType ) : TType { + var g = globals.get(name); + if( g != null ) { + return switch( g ) { + case TLazy(f): f(); + default: g; + } + } + if( allowAsync ) { + g = globals.get("a_"+name); + if( g != null ) g = unasync(g); + if( g != null ) return g; + } + switch( name ) { + case "null": + return makeMono(); + case "true", "false": + return TBool; + case "trace": + return TDynamic; + default: + #if hscriptPos + switch( withType ) { + // enum constructor resolution + case WithType(et = TEnum(e, args)): + for( c in e.constructors ) + if( c.name == name ) { + if( onTopDownEnum(e,name) ) { // deprecated + var ct = c.args == null ? et : TFun(c.args, et); + return apply(ct, e.params, args); + } + var acc = getTypeAccess(et, expr, name); + if( acc != null ) { + expr.e = acc; + var ct = c.args == null ? et : TFun(c.args, et); + return apply(ct, e.params, args); + } + break; + } + // abstract enum resolution + case WithType(at = TAbstract(a, args)) if( hasMeta(a.meta,":enum") ): + var f = a.impl.statics.get(name); + if( f != null && hasMeta(f.meta,":enum") ) { + var acc = getTypeAccess(TInst(a.impl,[]),expr,name); + if( acc != null ) { + expr.e = acc; + return at; + } + } + default: + } + // this variable resolution + var g = locals.get("this"); + if( g != null ) { + // local this resolution + var prev = checkPrivate; + checkPrivate = false; + var t = getField(g, name, expr); + checkPrivate = prev; + if( t != null ) { + expr.e = EField(mk(EIdent("this"),expr),name); + return t; + } + // static resolution + switch( g ) { + case TInst(c, _): + var f = c.statics.get(name); + if( f != null ) { + var acc = getTypeAccess(g, expr, name); + if( acc != null ) { + expr.e = acc; + return apply(f.t, f.params, [for( a in f.params ) makeMono()]); + } + } + default: + } + } + // type path resolution + var t = types.getType(name); + if( !t.match(TUnresolved(_)) ) { + var acc = getTypeAccess(t, expr); + if( acc != null ) { + expr.e = acc; + switch( t ) { + case TInst(c,_): + return TAnon([for( f in c.statics ) { name : f.name, t : f.t, opt : false }]); + case TEnum(e,_): + return TAnon([for( f in e.constructors ) { name : f.name, t : f.args == null ? t : TFun(f.args,t), opt : false }]); + default: + throw "assert"; + } + } + } + #end + } + return null; + } + + function getTypeAccess( t : TType, expr : Expr, ?field : String ) : ExprDef { + return null; + } + + function typeExpr( expr : Expr, withType : WithType ) : TType { + if( expr == null && isCompletion ) + return switch( withType ) { + case WithType(t): t; + default: TDynamic; + } + switch( edef(expr) ) { + case EConst(c): + return switch (c) { + case CInt(_): TInt; + case CFloat(_): TFloat; + case CString(_): types.t_string; + } + case EIdent(v): + return typePath([{ f : v, e : expr }],withType,false); + case EBlock(el): + var t = TVoid; + var locals = saveLocals(); + for( e in el ) + t = typeExpr(e, e == el[el.length-1] ? withType : NoValue); + this.locals = locals; + return t; + case EVar(n, t, init): + var vt = t == null ? makeMono() : makeType(t, expr); + if( init != null ) { + var et = typeExpr(init, t == null ? Value : WithType(vt)); + if( t == null ) vt = et else unify(et,vt, init); + } + locals.set(n, vt); + return TVoid; + case EParent(e): + return typeExpr(e,withType); + case ECall(e, params): + switch( edef(e) ) { + case EField(val, "bind"): + var ft = typeExpr(val, Value); + switch( ft ) { + case TFun(args,ret): + var remainArgs = args.copy(); + for( p in params ) { + var a = remainArgs.shift(); + if( a == null ) { + error("Too many arguments", p); + return TFun([], ret); + } + typeExprWith(p, a.t); + } + return TFun(remainArgs, ret); + default: + } + default: + } + var ft = typeExpr(e, switch( [edef(e),withType] ) { + case [EIdent(_),WithType(TEnum(_))]: withType; + default: Value; + }); + switch( follow(ft) ) { + case TFun(args, ret): + for( i in 0...params.length ) { + var a = args[i]; + if( a == null ) { + error("Too many arguments", params[i]); + break; + } + var t = typeExpr(params[i], a == null ? Value : WithType(a.t)); + unify(t, a.t, params[i]); + } + for( i in params.length...args.length ) + if( !args[i].opt ) + error("Missing argument "+args[i].name+":"+typeStr(args[i].t), expr); + return ret; + case TDynamic: + for( p in params ) typeExpr(p,Value); + return makeMono(); + default: + error(typeStr(ft)+" cannot be called", e); + return makeMono(); + } + case EField(o, f): + return typeField(o,f,expr,withType,false); + case ECheckType(v, t): + var ct = makeType(t, expr); + var vt = typeExpr(v, WithType(ct)); + unify(vt, ct, v); + return ct; + case EMeta(m, args, e): + return checkMeta(m,args,e,expr,withType); + case EIf(cond, e1, e2), ETernary(cond, e1, e2): + typeExprWith(cond, TBool); + var t1 = typeExpr(e1, withType); + if( e2 == null ) + return t1; + var t2 = typeExpr(e2, withType); + if( withType == NoValue ) + return TVoid; + if( tryUnify(t2,t1) ) + return t1; + if( tryUnify(t1,t2) ) + return t2; + unify(t2,t1,e2); // error + case EWhile(cond, e), EDoWhile(cond, e): + typeExprWith(cond,TBool); + typeExpr(e, NoValue); + return TVoid; + case EObject(fl): + switch( withType ) { + case WithType(follow(_) => TAnon(tfields)) if( tfields.length > 0 ): + var map = [for( f in tfields ) f.name => f]; + return TAnon([for( f in fl ) { + var ft = map.get(f.name); + var ft = if( ft == null ) { + error("Extra field "+f.name, f.e); + TDynamic; + } else ft.t; + { t : typeExprWith(f.e, ft), opt : false, name : f.name } + }]); + default: + return TAnon([for( f in fl ) { t : typeExpr(f.e, Value), opt : false, name : f.name }]); + } + case EBreak, EContinue: + return TVoid; + case EReturn(v): + var et = v == null ? TVoid : typeExpr(v, allowReturn == null ? Value : WithType(allowReturn)); + hasReturn = true; + if( allowReturn == null ) + error("Return not allowed here", expr); + else + unify(et, allowReturn, v == null ? expr : v); + return makeMono(); + case EArrayDecl(el): + var et = null; + for( v in el ) { + var t = typeExpr(v, et == null ? Value : WithType(et)); + if( et == null ) et = t else if( !tryUnify(t,et) ) { + if( tryUnify(et,t) ) et = t else unify(t,et,v); + } + } + if( et == null ) et = makeMono(); + return types.getType("Array",[et]); + case EArray(a, index): + typeExprWith(index, TInt); + var at = typeExpr(a, Value); + switch( follow(at) ) { + case TInst({ name : "Array"},[et]): return et; + default: error(typeStr(at)+" is not an Array", a); + } + case EThrow(e): + typeExpr(e, Value); + return makeMono(); + case EFunction(args, body, name, ret): + var ft = null, tret = null, targs = null; + if( currentFunType != null ) { + switch( currentFunType ) { + case TFun(args,ret): + ft = currentFunType; + tret = ret; targs = args; + default: + throw "assert"; + } + currentFunType = null; + } else { + tret = ret == null ? makeMono() : makeType(ret, expr); + } + var locals = saveLocals(); + var oldRet = allowReturn; + var oldGDef = allowDefine; + var oldHasRet = hasReturn; + allowReturn = tret; + allowDefine = false; + hasReturn = false; + var withArgs = null; + if( name != null && !withType.match(WithType(follow(_) => TFun(_))) ) { + var ev = events.get(name); + if( ev != null ) withType = WithType(ev); + } + var isShortFun = switch( edef(body) ) { + case EMeta(":lambda",_): true; + default: false; + } + switch( withType ) { + case WithType(follow(_) => TFun(args,ret)): + withArgs = args; + if( !isShortFun || follow(ret) != TVoid ) unify(tret,ret,expr); + default: + } + if( targs == null ) + targs = typeArgs(args,expr); + for( i in 0...targs.length ) { + var a = targs[i]; + if( withArgs != null ) { + if( i < withArgs.length ) + unify(withArgs[i].t, a.t, expr); + else + error("Extra argument "+a.name, expr); + } + this.locals.set(a.name, a.t); + } + if( withArgs != null && targs.length < withArgs.length ) + error("Missing "+(withArgs.length - targs.length)+" arguments ("+[for( i in targs.length...withArgs.length ) typeStr(withArgs[i].t)].join(",")+")", expr); + typeExpr(body,NoValue); + if( !hasReturn && !tryUnify(tret, TVoid) ) + error("Missing return "+typeStr(tret), expr); + allowDefine = oldGDef; + allowReturn = oldRet; + hasReturn = oldHasRet; + this.locals = locals; + if( ft == null ) { + ft = TFun(targs, tret); + if( name != null ) locals.set(name, ft); + } + return ft; + case EUnop(op, _, e): + var et = typeExpr(e, Value); + switch( op ) { + case "++", "--", "-": + unify(et,TInt,e); + return et; + case "!": + unify(et,TBool,e); + return et; + default: + } + case EFor(v, it, e): + var locals = saveLocals(); + var itt = typeExpr(it, Value); + var vt = getIteratorType(itt, it); + this.locals.set(v, vt); + typeExpr(e, NoValue); + this.locals = locals; + return TVoid; + case EForGen(it, e): + Tools.getKeyIterator(it,function(vk,vv,it) { + if( vk == null ) { + error("Invalid for expression", it); + return; + } + var locals = saveLocals(); + var itt = typeExpr(it, Value); + var types = getKeyIteratorTypes(itt, it); + this.locals.set(vk, types.key); + this.locals.set(vv, types.value); + typeExpr(e, NoValue); + this.locals = locals; + }); + return TVoid; + case EBinop(op, e1, e2): + switch( op ) { + case "&", "|", "^", ">>", ">>>", "<<": + typeExprWith(e1,TInt); + typeExprWith(e2,TInt); + return TInt; + case "=": + if( allowDefine ) { + switch( edef(e1) ) { + case EIdent(i) if( !locals.exists(i) && !globals.exists(i) ): + var vt = typeExpr(e2,Value); + locals.set(i, vt); + return vt; + default: + } + } + var vt = switch( edef(e1) ) { + case EIdent(v): typePath([{f:v,e:e1}],withType,true); + case EField(o,f): typeField(o, f, e1, withType, true); + default: typeExpr(e1,Value); + } + typeExprWith(e2,vt); + return vt; + case "+": + var t1 = typeExpr(e1,WithType(TInt)); + var t2 = typeExpr(e2,WithType(t1)); + tryUnify(t1,t2); + switch( [follow(t1), follow(t2)]) { + case [TInt, TInt]: + return TInt; + case [TFloat, TInt], [TInt, TFloat], [TFloat, TFloat]: + return TFloat; + case [TDynamic, _], [_, TDynamic]: + return TDynamic; + case [t1,t2]: + if( isString(t1) || isString(t2) ) + return types.t_string; + unify(t1, TFloat, e1); + unify(t2, TFloat, e2); + } + case "-", "*", "/", "%": + var t1 = typeExpr(e1,WithType(TInt)); + var t2 = typeExpr(e2,WithType(t1)); + if( !tryUnify(t1,t2) ) + unify(t2,t1,e2); + switch( [follow(t1), follow(t2)]) { + case [TInt, TInt]: + if( op == "/" ) return TFloat; + return TInt; + case [TFloat|TDynamic, TInt|TDynamic], [TInt|TDynamic, TFloat|TDynamic], [TFloat, TFloat]: + return TFloat; + default: + unify(t1, TFloat, e1); + unify(t2, TFloat, e2); + } + case "&&", "||": + typeExprWith(e1,TBool); + typeExprWith(e2,TBool); + return TBool; + case "...": + typeExprWith(e1,TInt); + typeExprWith(e2,TInt); + return makeIterator(TInt); + case "==", "!=": + var t1 = typeExpr(e1,Value); + var t2 = typeExpr(e2,WithType(t1)); + if( !tryUnify(t1,t2) ) + unify(t2,t1,e2); + return TBool; + case ">", "<", ">=", "<=": + var t1 = typeExpr(e1,Value); + var t2 = typeExpr(e2,WithType(t1)); + if( !tryUnify(t1,t2) ) + unify(t2,t1,e2); + switch( follow(t1) ) { + case TInt, TFloat, TBool, TInst({name:"String"},_): + default: + error("Cannot compare "+typeStr(t1), expr); + } + return TBool; + default: + if( op.charCodeAt(op.length-1) == "=".code ) { + var t = typeExpr(mk(EBinop(op.substr(0,op.length-1),e1,e2),expr),withType); + return typeExpr(mk(EBinop("=",e1,e2),expr), withType); + } + error("Unsupported operation "+op, expr); + } + case ETry(etry, v, et, ecatch): + var vt = typeExpr(etry, withType); + + var old = locals.get(v); + locals.set(v, makeType(et, ecatch)); + var ct = typeExpr(ecatch, withType); + if( old != null ) locals.set(v,old) else locals.remove(v); + + if( withType == NoValue ) + return TVoid; + if( tryUnify(vt,ct) ) + return ct; + unify(ct,vt,ecatch); + return vt; + case ESwitch(value, cases, defaultExpr): + var tmin = null; + var vt = typeExpr(value, Value); + inline function mergeType(t,p) { + if( withType != NoValue ) { + if( tmin == null ) + tmin = t; + else if( !tryUnify(t,tmin) ) { + unify(tmin,t, p); + tmin = t; + } + } + } + for( c in cases ) { + for( v in c.values ) { + var ct = typeExpr(v, WithType(vt)); + unify(ct, vt, v); + } + var et = typeExpr(c.expr, withType); + mergeType(et, c.expr); + } + if( defaultExpr != null ) + mergeType( typeExpr(defaultExpr, withType), defaultExpr); + return withType == NoValue ? TVoid : tmin == null ? makeMono() : tmin; + case ECast(e,t): + var et = typeExpr(e, Value); + return t == null ? makeMono() : makeType(t,expr); + case ENew(cl, params): + } + error("Don't know how to type "+edef(expr).getName(), expr); + return TDynamic; + } + + function checkMeta( m : String, args : Array, next : Expr, expr : Expr, withType ) { + if( m == ":untyped" && allowUntypedMeta ) + return makeMono(); + return typeExpr(next, withType); + } + + function getIteratorType( itt : TType, it : Expr ) { + switch( follow(itt) ) { + case TInst({name:"Array"},[t]): + return t; + default: + } + var ft = getField(itt,"iterator", it); + if( ft == null ) + switch( itt ) { + case TAbstract(a, args): + // special case : we allow unconditional access + // to an abstract iterator() underlying value (eg: ArrayProxy) + var at = apply(a.t,a.params,args); + return getIteratorType(at, it); + default: + } + if( ft != null ) + switch( ft ) { + case TFun([],ret): ft = ret; + default: ft = null; + } + var t = makeMono(); + var iter = makeIterator(t); + unify(ft != null ? ft : itt,iter,it); + return t; + } + + + function getKeyIteratorTypes( itt : TType, it : Expr ) { + switch( follow(itt) ) { + case TInst({name:"Array"},[t]): + return { key : TInt, value : t }; + default: + } + var ft = getField(itt,"keyValueIterator", it); + if( ft == null ) + switch( itt ) { + case TAbstract(a, args): + // special case : we allow unconditional access + // to an abstract keyValueIterator() underlying value (eg: ArrayProxy) + var at = apply(a.t,a.params,args); + return getKeyIteratorTypes(at, it); + default: + } + if( ft != null ) + switch( ft ) { + case TFun([],ret): ft = ret; + default: ft = null; + } + var key = makeMono(); + var value = makeMono(); + var iter = makeIterator(TAnon([{name:"key",t:key,opt:false},{name:"value",t:value,opt:false}])); + unify(ft != null ? ft : itt,iter,it); + return { key : key, value : value }; + } + + +} \ No newline at end of file diff --git a/lib/hscript/hscript/Expr.hx b/lib/hscript/hscript/Expr.hx new file mode 100644 index 00000000..f9661038 --- /dev/null +++ b/lib/hscript/hscript/Expr.hx @@ -0,0 +1,180 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; + +enum Const { + CInt( v : Int ); + CFloat( f : Float ); + CString( s : String ); +} + +#if hscriptPos +typedef Expr = { + var e : ExprDef; + var pmin : Int; + var pmax : Int; + var origin : String; + var line : Int; +} +enum ExprDef { +#else +typedef ExprDef = Expr; +enum Expr { +#end + EConst( c : Const ); + EIdent( v : String ); + EVar( n : String, ?t : CType, ?e : Expr ); + EParent( e : Expr ); + EBlock( e : Array ); + EField( e : Expr, f : String ); + EBinop( op : String, e1 : Expr, e2 : Expr ); + EUnop( op : String, prefix : Bool, e : Expr ); + ECall( e : Expr, params : Array ); + EIf( cond : Expr, e1 : Expr, ?e2 : Expr ); + EWhile( cond : Expr, e : Expr ); + EFor( v : String, it : Expr, e : Expr ); + EBreak; + EContinue; + EFunction( args : Array, e : Expr, ?name : String, ?ret : CType ); + EReturn( ?e : Expr ); + EArray( e : Expr, index : Expr ); + EArrayDecl( e : Array ); + ENew( cl : String, params : Array ); + EThrow( e : Expr ); + ETry( e : Expr, v : String, t : Null, ecatch : Expr ); + EObject( fl : Array<{ name : String, e : Expr }> ); + ETernary( cond : Expr, e1 : Expr, e2 : Expr ); + ESwitch( e : Expr, cases : Array<{ values : Array, expr : Expr }>, ?defaultExpr : Expr); + EDoWhile( cond : Expr, e : Expr); + EMeta( name : String, args : Array, e : Expr ); + ECheckType( e : Expr, t : CType ); + EForGen( it : Expr, e : Expr ); + ECast( e : Expr, ?t : CType ); +} + +typedef Argument = { name : String, ?t : CType, ?opt : Bool, ?value : Expr }; + +typedef Metadata = Array<{ name : String, params : Array }>; + +enum CType { + CTPath( path : Array, ?params : Array ); + CTFun( args : Array, ret : CType ); + CTAnon( fields : Array<{ name : String, t : CType, ?meta : Metadata }> ); + CTParent( t : CType ); + CTOpt( t : CType ); + CTNamed( n : String, t : CType ); + CTExpr( e : Expr ); // for type parameters only +} + +#if hscriptPos +class Error { + public var e : ErrorDef; + public var pmin : Int; + public var pmax : Int; + public var origin : String; + public var line : Int; + public function new(e, pmin, pmax, origin, line) { + this.e = e; + this.pmin = pmin; + this.pmax = pmax; + this.origin = origin; + this.line = line; + } + public function toString(): String { + return Printer.errorToString(this); + } +} +enum ErrorDef { +#else +enum Error { +#end + EInvalidChar( c : Int ); + EUnexpected( s : String ); + EUnterminatedString; + EUnterminatedComment; + EInvalidPreprocessor( msg : String ); + EUnknownVariable( v : String ); + EInvalidIterator( v : String ); + EInvalidOp( op : String ); + EInvalidAccess( f : String ); + ECustom( msg : String ); +} + + +enum ModuleDecl { + DPackage( path : Array ); + DImport( path : Array, ?everything : Bool, ?name : String ); + DClass( c : ClassDecl ); + DTypedef( c : TypeDecl ); +} + +typedef ModuleType = { + var name : String; + var params : {}; // TODO : not yet parsed + var meta : Metadata; + var isPrivate : Bool; +} + +typedef ClassDecl = {> ModuleType, + var extend : Null; + var implement : Array; + var fields : Array; + var isExtern : Bool; +} + +typedef TypeDecl = {> ModuleType, + var t : CType; +} + +typedef FieldDecl = { + var name : String; + var meta : Metadata; + var kind : FieldKind; + var access : Array; +} + +enum FieldAccess { + APublic; + APrivate; + AInline; + AOverride; + AStatic; + AMacro; +} + +enum FieldKind { + KFunction( f : FunctionDecl ); + KVar( v : VarDecl ); +} + +typedef FunctionDecl = { + var args : Array; + var expr : Expr; + var ret : Null; +} + +typedef VarDecl = { + var get : Null; + var set : Null; + var expr : Null; + var type : Null; +} diff --git a/lib/hscript/hscript/Interp.hx b/lib/hscript/hscript/Interp.hx new file mode 100644 index 00000000..c8d5c6fe --- /dev/null +++ b/lib/hscript/hscript/Interp.hx @@ -0,0 +1,745 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import haxe.PosInfos; +import hscript.Expr; +import haxe.Constraints.IMap; + +private enum Stop { + SBreak; + SContinue; + SReturn; +} + +class Interp { + + public var variables : Map; + var locals : Map; + var binops : Map Expr -> Dynamic >; + + var depth : Int; + var inTry : Bool; + var declared : Array<{ n : String, old : { r : Dynamic } }>; + var returnValue : Dynamic; + + #if hscriptPos + var curExpr : Expr; + #end + + public function new() { + locals = new Map(); + declared = new Array(); + resetVariables(); + initOps(); + } + + private function resetVariables(){ + variables = new Map(); + variables.set("null",null); + variables.set("true",true); + variables.set("false",false); + variables.set("trace", Reflect.makeVarArgs(function(el) { + var inf = posInfos(); + var v = el.shift(); + if( el.length > 0 ) inf.customParams = el; + haxe.Log.trace(Std.string(v), inf); + })); + } + + public function posInfos(): PosInfos { + #if hscriptPos + if (curExpr != null) + return cast { fileName : curExpr.origin, lineNumber : curExpr.line }; + #end + return cast { fileName : "hscript", lineNumber : 0 }; + } + + function initOps() { + var me = this; + binops = new Map(); + binops.set("+",function(e1,e2) return me.expr(e1) + me.expr(e2)); + binops.set("-",function(e1,e2) return me.expr(e1) - me.expr(e2)); + binops.set("*",function(e1,e2) return me.expr(e1) * me.expr(e2)); + binops.set("/",function(e1,e2) return me.expr(e1) / me.expr(e2)); + binops.set("%",function(e1,e2) return me.expr(e1) % me.expr(e2)); + binops.set("&",function(e1,e2) return me.expr(e1) & me.expr(e2)); + binops.set("|",function(e1,e2) return me.expr(e1) | me.expr(e2)); + binops.set("^",function(e1,e2) return me.expr(e1) ^ me.expr(e2)); + binops.set("<<",function(e1,e2) return me.expr(e1) << me.expr(e2)); + binops.set(">>",function(e1,e2) return me.expr(e1) >> me.expr(e2)); + binops.set(">>>",function(e1,e2) return me.expr(e1) >>> me.expr(e2)); + binops.set("==",function(e1,e2) return me.expr(e1) == me.expr(e2)); + binops.set("!=",function(e1,e2) return me.expr(e1) != me.expr(e2)); + binops.set(">=",function(e1,e2) return me.expr(e1) >= me.expr(e2)); + binops.set("<=",function(e1,e2) return me.expr(e1) <= me.expr(e2)); + binops.set(">",function(e1,e2) return me.expr(e1) > me.expr(e2)); + binops.set("<",function(e1,e2) return me.expr(e1) < me.expr(e2)); + binops.set("||",function(e1,e2) return me.expr(e1) == true || me.expr(e2) == true); + binops.set("&&",function(e1,e2) return me.expr(e1) == true && me.expr(e2) == true); + binops.set("=",assign); + binops.set("...",function(e1,e2) return new IntIterator(me.expr(e1),me.expr(e2))); + binops.set("is",function(e1,e2) return #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (me.expr(e1), me.expr(e2))); + assignOp("+=",function(v1:Dynamic,v2:Dynamic) return v1 + v2); + assignOp("-=",function(v1:Float,v2:Float) return v1 - v2); + assignOp("*=",function(v1:Float,v2:Float) return v1 * v2); + assignOp("/=",function(v1:Float,v2:Float) return v1 / v2); + assignOp("%=",function(v1:Float,v2:Float) return v1 % v2); + assignOp("&=",function(v1,v2) return v1 & v2); + assignOp("|=",function(v1,v2) return v1 | v2); + assignOp("^=",function(v1,v2) return v1 ^ v2); + assignOp("<<=",function(v1,v2) return v1 << v2); + assignOp(">>=",function(v1,v2) return v1 >> v2); + assignOp(">>>=",function(v1,v2) return v1 >>> v2); + } + + function setVar( name : String, v : Dynamic ) { + variables.set(name, v); + return v; + } + + function assign( e1 : Expr, e2 : Expr ) : Dynamic { + var v = expr(e2); + switch( Tools.expr(e1) ) { + case EIdent(id): + var l = locals.get(id); + if( l == null ) + setVar(id,v) + else + l.r = v; + case EField(e,f): + v = set(expr(e),f,v); + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + setMapValue(arr, index, v); + } + else { + arr[index] = v; + } + + default: + error(EInvalidOp("=")); + } + return v; + } + + function assignOp( op, fop : Dynamic -> Dynamic -> Dynamic ) { + var me = this; + binops.set(op,function(e1,e2) return me.evalAssignOp(op,fop,e1,e2)); + } + + function evalAssignOp(op,fop,e1,e2) : Dynamic { + var v; + switch( Tools.expr(e1) ) { + case EIdent(id): + var l = locals.get(id); + v = fop(expr(e1),expr(e2)); + if( l == null ) + setVar(id,v) + else + l.r = v; + case EField(e,f): + var obj = expr(e); + v = fop(get(obj,f),expr(e2)); + v = set(obj,f,v); + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + v = fop(getMapValue(arr, index), expr(e2)); + setMapValue(arr, index, v); + } + else { + v = fop(arr[index],expr(e2)); + arr[index] = v; + } + default: + return error(EInvalidOp(op)); + } + return v; + } + + function increment( e : Expr, prefix : Bool, delta : Int ) : Dynamic { + #if hscriptPos + curExpr = e; + var e = e.e; + #end + switch(e) { + case EIdent(id): + var l = locals.get(id); + var v : Dynamic = (l == null) ? resolve(id) : l.r; + if( prefix ) { + v += delta; + if( l == null ) setVar(id,v) else l.r = v; + } else + if( l == null ) setVar(id,v + delta) else l.r = v + delta; + return v; + case EField(e,f): + var obj = expr(e); + var v : Dynamic = get(obj,f); + if( prefix ) { + v += delta; + set(obj,f,v); + } else + set(obj,f,v + delta); + return v; + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + var v = getMapValue(arr, index); + if (prefix) { + v += delta; + setMapValue(arr, index, v); + } + else { + setMapValue(arr, index, v + delta); + } + return v; + } + else { + var v = arr[index]; + if( prefix ) { + v += delta; + arr[index] = v; + } else + arr[index] = v + delta; + return v; + } + default: + return error(EInvalidOp((delta > 0)?"++":"--")); + } + } + + public function execute( expr : Expr ) : Dynamic { + depth = 0; + locals = new Map(); + declared = new Array(); + return exprReturn(expr); + } + + function exprReturn(e) : Dynamic { + try { + return expr(e); + } catch( e : Stop ) { + switch( e ) { + case SBreak: throw "Invalid break"; + case SContinue: throw "Invalid continue"; + case SReturn: + var v = returnValue; + returnValue = null; + return v; + } + } + return null; + } + + function duplicate( h : Map ) { + var h2 = new Map(); + for( k in h.keys() ) + h2.set(k,h.get(k)); + return h2; + } + + function restore( old : Int ) { + while( declared.length > old ) { + var d = declared.pop(); + locals.set(d.n,d.old); + } + } + + inline function error(e : #if hscriptPos ErrorDef #else Error #end, rethrow=false ) : Dynamic { + #if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end + if( rethrow ) this.rethrow(e) else throw e; + return null; + } + + inline function rethrow( e : Dynamic ) { + #if hl + hl.Api.rethrow(e); + #else + throw e; + #end + } + + function resolve( id : String ) : Dynamic { + var v = variables.get(id); + if( v == null && !variables.exists(id) ) + error(EUnknownVariable(id)); + return v; + } + + public function expr( e : Expr ) : Dynamic { + #if hscriptPos + curExpr = e; + var e = e.e; + #end + switch( e ) { + case EConst(c): + switch( c ) { + case CInt(v): return v; + case CFloat(f): return f; + case CString(s): return s; + } + case EIdent(id): + var l = locals.get(id); + if( l != null ) + return l.r; + return resolve(id); + case EVar(n,_,e): + declared.push({ n : n, old : locals.get(n) }); + locals.set(n,{ r : (e == null)?null:expr(e) }); + return null; + case EParent(e): + return expr(e); + case EBlock(exprs): + var old = declared.length; + var v = null; + for( e in exprs ) + v = expr(e); + restore(old); + return v; + case EField(e,f): + return get(expr(e),f); + case EBinop(op,e1,e2): + var fop = binops.get(op); + if( fop == null ) error(EInvalidOp(op)); + return fop(e1,e2); + case EUnop(op,prefix,e): + switch(op) { + case "!": + return expr(e) != true; + case "-": + return -expr(e); + case "++": + return increment(e,prefix,1); + case "--": + return increment(e,prefix,-1); + case "~": + return ~expr(e); + default: + error(EInvalidOp(op)); + } + case ECall(e,params): + var args = new Array(); + for( p in params ) + args.push(expr(p)); + + switch( Tools.expr(e) ) { + case EField(e,f): + var obj = expr(e); + if( obj == null ) error(EInvalidAccess(f)); + return fcall(obj,f,args); + default: + return call(null,expr(e),args); + } + case EIf(econd,e1,e2): + return if( expr(econd) == true ) expr(e1) else if( e2 == null ) null else expr(e2); + case EWhile(econd,e): + whileLoop(econd,e); + return null; + case EDoWhile(econd,e): + doWhileLoop(econd,e); + return null; + case EFor(v,it,e): + forLoop(v,it,e); + return null; + case EForGen(it,e): + Tools.getKeyIterator(it, function(vk,vv,it) { + if( vk == null ) { + #if hscriptPos + curExpr = it; + #end + error(ECustom("Invalid for expression")); + return; + } + forKeyValueLoop(vk,vv,it,e); + }); + return null; + case EBreak: + throw SBreak; + case EContinue: + throw SContinue; + case EReturn(e): + returnValue = e == null ? null : expr(e); + throw SReturn; + case EFunction(params,fexpr,name,_): + var capturedLocals = duplicate(locals); + var me = this; + var hasOpt = false, minParams = 0; + for( p in params ) + if( p.opt ) + hasOpt = true; + else + minParams++; + var f = function(args:Array) { + if( ( (args == null) ? 0 : args.length ) != params.length ) { + if( args.length < minParams ) { + var str = "Invalid number of parameters. Got " + args.length + ", required " + minParams; + if( name != null ) str += " for function '" + name+"'"; + error(ECustom(str)); + } + // make sure mandatory args are forced + var args2 = []; + var extraParams = args.length - minParams; + var pos = 0; + for( p in params ) + if( p.opt ) { + if( extraParams > 0 ) { + args2.push(args[pos++]); + extraParams--; + } else + args2.push(null); + } else + args2.push(args[pos++]); + args = args2; + } + var old = me.locals, depth = me.depth; + me.depth++; + me.locals = me.duplicate(capturedLocals); + for( i in 0...params.length ) + me.locals.set(params[i].name,{ r : args[i] }); + var r = null; + var oldDecl = declared.length; + if( inTry ) + try { + r = me.exprReturn(fexpr); + } catch( e : Dynamic ) { + restore(oldDecl); + me.locals = old; + me.depth = depth; + #if neko + neko.Lib.rethrow(e); + #else + throw e; + #end + } + else + r = me.exprReturn(fexpr); + restore(oldDecl); + me.locals = old; + me.depth = depth; + return r; + }; + var f = Reflect.makeVarArgs(f); + if( name != null ) { + if( depth == 0 ) { + // global function + variables.set(name, f); + } else { + // function-in-function is a local function + declared.push( { n : name, old : locals.get(name) } ); + var ref = { r : f }; + locals.set(name, ref); + capturedLocals.set(name, ref); // allow self-recursion + } + } + return f; + case EArrayDecl(arr): + if( arr.length > 0 && Tools.expr(arr[0]).match(EBinop("=>", _)) ) { + var keys = []; + var values = []; + for( e in arr ) { + switch(Tools.expr(e)) { + case EBinop("=>", eKey, eValue): + keys.push(expr(eKey)); + values.push(expr(eValue)); + default: + #if hscriptPos + curExpr = e; + #end + error(ECustom("Invalid map key=>value expression")); + } + } + return makeMap(keys,values); + } else { + var a = new Array(); + for( e in arr ) + a.push(expr(e)); + return a; + } + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if( isMap(arr) ) + return getMapValue(arr, index); + return arr[index]; + case ENew(cl,params): + var a = new Array(); + for( e in params ) + a.push(expr(e)); + return cnew(cl,a); + case EThrow(e): + throw expr(e); + case ETry(e,n,_,ecatch): + var old = declared.length; + var oldTry = inTry; + try { + inTry = true; + var v : Dynamic = expr(e); + restore(old); + inTry = oldTry; + return v; + } catch( err : Stop ) { + inTry = oldTry; + throw err; + } catch( err : Dynamic ) { + // restore vars + restore(old); + inTry = oldTry; + // declare 'v' + declared.push({ n : n, old : locals.get(n) }); + locals.set(n,{ r : err }); + var v : Dynamic = expr(ecatch); + restore(old); + return v; + } + case EObject(fl): + var o = {}; + for( f in fl ) + set(o,f.name,expr(f.e)); + return o; + case ETernary(econd,e1,e2): + return if( expr(econd) == true ) expr(e1) else expr(e2); + case ESwitch(e, cases, def): + var val : Dynamic = expr(e); + var match = false; + for( c in cases ) { + for( v in c.values ) + if( expr(v) == val ) { + match = true; + break; + } + if( match ) { + val = expr(c.expr); + break; + } + } + if( !match ) + val = def == null ? null : expr(def); + return val; + case EMeta(meta, args, e): + return exprMeta(meta, args, e); + case ECheckType(e,_), ECast(e,_): + return expr(e); + } + return null; + } + + function exprMeta(meta,args,e) : Dynamic { + return expr(e); + } + + function doWhileLoop(econd,e) { + var old = declared.length; + do { + if( !loopRun(() -> expr(e)) ) + break; + } + while( expr(econd) == true ); + restore(old); + } + + function whileLoop(econd,e) { + var old = declared.length; + while( expr(econd) == true ) { + if( !loopRun(() -> expr(e)) ) + break; + } + restore(old); + } + + function makeIterator( v : Dynamic ) : Iterator { + #if js + // don't use try/catch (very slow) + if( v is Array ) + return (v : Array).iterator(); + if( v.iterator != null ) v = v.iterator(); + #else + #if (cpp) if ( v.iterator != null ) #end + try v = v.iterator() catch( e : Dynamic ) {}; + #end + if( v.hasNext == null || v.next == null ) error(EInvalidIterator(v)); + return v; + } + + function makeKeyValueIterator( v : Dynamic ) : KeyValueIterator { + #if js + // don't use try/catch (very slow) + if( v is Array ) + return (v : Array).keyValueIterator(); + if( v.keyValueIterator != null ) v = v.keyValueIterator(); + #else + try v = v.keyValueIterator() catch( e : Dynamic ) {}; + #end + if( v.hasNext == null || v.next == null ) error(EInvalidIterator(v)); + return v; + } + + function forLoop(n,it,e) { + var old = declared.length; + declared.push({ n : n, old : locals.get(n) }); + var it = makeIterator(expr(it)); + while( it.hasNext() ) { + locals.set(n,{ r : it.next() }); + if( !loopRun(() -> expr(e)) ) + break; + } + restore(old); + } + + function forKeyValueLoop(vk,vv,it,e) { + var old = declared.length; + declared.push({ n : vk, old : locals.get(vk) }); + declared.push({ n : vv, old : locals.get(vv) }); + var it = makeKeyValueIterator(expr(it)); + while( it.hasNext() ) { + var v = it.next(); + locals.set(vk,{ r : v.key }); + locals.set(vv,{ r : v.value }); + if( !loopRun(() -> expr(e)) ) + break; + } + restore(old); + } + + inline function loopRun( f : Void -> Void ) { + var cont = true; + try { + f(); + } catch( err : Stop ) { + switch( err ) { + case SContinue: + case SBreak: + cont = false; + case SReturn: + throw err; + } + } + return cont; + } + + inline function isMap(o:Dynamic):Bool { + return (o is IMap); + } + + inline function getMapValue(map:Dynamic, key:Dynamic):Dynamic { + return cast(map, haxe.Constraints.IMap).get(key); + } + + inline function setMapValue(map:Dynamic, key:Dynamic, value:Dynamic):Void { + cast(map, haxe.Constraints.IMap).set(key, value); + } + + function makeMap( keys : Array, values : Array ) : Dynamic { + var isAllString:Bool = true; + var isAllInt:Bool = true; + var isAllObject:Bool = true; + var isAllEnum:Bool = true; + for( key in keys ) { + isAllString = isAllString && (key is String); + isAllInt = isAllInt && (key is Int); + isAllObject = isAllObject && Reflect.isObject(key); + isAllEnum = isAllEnum && Reflect.isEnumValue(key); + } + + #if (haxe_ver >= 4.1) + if( isAllInt ) { + var m = new Map(); + for( i => key in keys ) + m.set(key, values[i]); + return m; + } + if( isAllString ) { + var m = new Map(); + for( i => key in keys ) + m.set(key, values[i]); + return m; + } + if( isAllEnum ) { + var m = new haxe.ds.EnumValueMap(); + for( i => key in keys ) + m.set(key, values[i]); + return m; + } + if( isAllObject ) { + var m = new Map<{},Dynamic>(); + for( i => key in keys ) + m.set(key, values[i]); + return m; + } + #else + var m:Dynamic = { + if ( isAllInt ) new haxe.ds.IntMap(); + else if ( isAllString ) new haxe.ds.StringMap(); + else if ( isAllEnum ) new haxe.ds.EnumValueMap(); + else if ( isAllObject ) new haxe.ds.ObjectMap(); + else null; + } + if( m != null ) { + for ( n in 0...keys.length ) + setMapValue(m, keys[n], values[n]); + return m; + } + #end + error(ECustom("Invalid map keys "+keys)); + return null; + } + + function get( o : Dynamic, f : String ) : Dynamic { + if ( o == null ) error(EInvalidAccess(f)); + return { + #if php + // https://github.com/HaxeFoundation/haxe/issues/4915 + try { + Reflect.getProperty(o, f); + } catch (e:Dynamic) { + Reflect.field(o, f); + } + #else + Reflect.getProperty(o, f); + #end + } + } + + function set( o : Dynamic, f : String, v : Dynamic ) : Dynamic { + if( o == null ) error(EInvalidAccess(f)); + Reflect.setProperty(o,f,v); + return v; + } + + function fcall( o : Dynamic, f : String, args : Array ) : Dynamic { + return call(o, get(o, f), args); + } + + function call( o : Dynamic, f : Dynamic, args : Array ) : Dynamic { + return Reflect.callMethod(o,f,args); + } + + function cnew( cl : String, args : Array ) : Dynamic { + var c = Type.resolveClass(cl); + if( c == null ) c = resolve(cl); + return Type.createInstance(c,args); + } + +} diff --git a/lib/hscript/hscript/JsInterp.hx b/lib/hscript/hscript/JsInterp.hx new file mode 100644 index 00000000..2c8bbd9b --- /dev/null +++ b/lib/hscript/hscript/JsInterp.hx @@ -0,0 +1,448 @@ +package hscript; + +class JsInterp extends Interp { + + /** + Variables declared in `ctx` are directly accessed without going through the `variables` map, which is faster + **/ + public var ctx : {}; + + /** + If properties are defined, all calls to get/set are only done for fields accesses which are listed here. + **/ + public var properties : Map; + + + var localNames : Map; + var hasBreakContinue : Int = 0; + var hasReturn : Bool; + + override function execute( expr : Expr ) : Dynamic { + depth = 0; + localNames = new Map(); + var str = '(($$i) => ${exprValue(expr)})'; + var f : Dynamic -> Dynamic = js.Lib.eval(str); + return f(this); + } + + public static function defineArrayExtensions() { + var arr : Dynamic = Array; + arr.prototype.copy = function() { var v : Array = js.Lib.nativeThis; return [for( v in v ) v]; }; + arr.prototype.contains = function(i) return js.Lib.nativeThis.indexOf(i) >= 0; + arr.prototype.remove = function(x) return @:privateAccess HxOverrides.remove(js.Lib.nativeThis, x); + arr.prototype.resize = function(len) js.Lib.nativeThis.length = len; + } + + function escapeString(s:String) { + return s.split("\\").join("\\\\").split("\r").join("\\r").split("\n").join("\\n").split('"').join('\\"'); + } + + function handleRBC( e : Expr ) { + function mkRBC(code:Int,?value) { + var fields = [{ name : "$", e : Tools.mk(EConst(CInt(code)),e) }]; + if( value != null ) fields.push({name:"$val",e:value}); + return Tools.mk(EThrow(Tools.mk(EObject(fields),e)),e); + } + switch( Tools.expr(e) ) { + case EReturn(v): + hasReturn = true; + return mkRBC(1,v); + case EBreak if( hasBreakContinue >= 0 ): + hasBreakContinue |= 1; + return mkRBC(2); + case EContinue if( hasBreakContinue >= 0 ): + hasBreakContinue |= 2; + return mkRBC(3); + case EWhile(_), EFor(_), EDoWhile(_): + var prev = hasBreakContinue; + hasBreakContinue = -1; + e = Tools.map(e, handleRBC); + hasBreakContinue = prev; + return e; + case EFunction(_): + return e; + default: + return Tools.map(e, handleRBC); + } + } + + function exprValue( expr : Expr ) { + switch( Tools.expr(expr) ) { + case EBlock([]): + return "null"; + case EIf(cond,e1,e2): + return exprJS(Tools.mk(ETernary(cond,e1,e2),expr)); + case EBlock(el): + var el = [for( e in el ) handleRBC(e)]; + var last = el[el.length-1]; + switch( Tools.expr(last) ) { + case EFunction(_,_,name,_) if( name != null ): // don't return latest named function + default: + el[el.length - 1] = Tools.mk(EReturn(last),last); + } + var ebl = Tools.mk(EBlock(el),expr); + return '(() => ${exprJS(ebl)})()'; + case ETry(e,v,t,ecatch): + e = handleRBC(e); + ecatch = handleRBC(ecatch); + var expr = Tools.mk(ETry(Tools.mk(EReturn(e),e),v,t,Tools.mk(EReturn(ecatch),ecatch)),expr); + return '(() => { ${exprJS(expr)} })()'; + case EVar(_,_,e): + return e == null ? "null" : '(${exprValue(e)},null)'; + case EWhile(_), EFor(_), EDoWhile(_), EThrow(_): + expr = handleRBC(expr); + return '(() => {${exprJS(expr)}})()'; + case EMeta(_,_,e), ECheckType(e,_): + return exprValue(e); + case EFunction(_,_,name,_) if( name != null ): + return '(() => {${exprJS(expr)}})()'; + default: + return exprJS(expr); + } + } + + function exprBlock( expr : Expr ) { + switch( Tools.expr(expr) ) { + case EBlock(_): + return exprJS(expr); + default: + return '{${exprJS(expr)};}'; + } + } + + function addPos( estr : String ) { + #if hscriptPos + var expr = curExpr; + var p = '{pmin:,pmax:,origin:"",line:}'; + return '($$i._p(${expr.pmin},${expr.pmax},"${expr.origin}",${expr.line}),$estr)'; + #else + return estr; + #end + } + + function isContext(v:String) { + return ctx != null && Reflect.hasField(ctx,v); + } + + function isProperty(f:String) { + return properties == null || properties.get(f); + } + + function exprCond( e : Expr ) { + return switch( Tools.expr(e) ) { + case EBinop("=="|"!="|">="|">"|"<="|"<"|"&&"|"||",_): exprValue(e); + default: '(${exprOp(e)} == true)'; + } + } + + function exprOp( e : Expr ) { + return switch( Tools.expr(e) ) { + case EBinop(_), EUnop(_): '(${exprValue(e)})'; + default: exprValue(e); + } + } + + function declLocal( n : String ) { + if( !localNames.exists(n) ) { + localNames.set(n, n); + return n; + } + var c = 2; + while( localNames.exists(n+c) ) c++; + localNames.set(n, n+c); + return n+c; + } + + function exprBreakContinue( e, needBlock=true ) { + var prevBC = hasBreakContinue; + hasBreakContinue = 0; + var estr = needBlock ? exprBlock(e) : exprJS(e); + if( hasBreakContinue != 0 ) { + var checks = []; + if( hasBreakContinue & 1 != 0 ) + checks.push("if( $e.$ == 2 ) break;"); + if( hasBreakContinue & 2 != 0 ) + checks.push("if( $e.$ == 3 ) continue;"); + if( !needBlock && !Tools.expr(e).match(EBlock(_)) ) + estr = '{$estr;}'; + estr = 'try $estr catch( $$e ) { ${checks.join('')} throw $$e; }'; + } + hasBreakContinue = prevBC; + return estr; + } + + function exprJS( expr : Expr ) : String { + #if hscriptPos + curExpr = expr; + var expr = expr.e; + #end + switch( expr ) { + case EConst(c): + return switch c { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): '"'+escapeString(s)+'"'; + } + case EIdent(v): + var v2 = localNames.get(v); + if( v2 != null ) + return v2; + if( isContext(v) ) + return '$$i.ctx.$v'; + switch( v ) { + case "null", "true", "false": return v; + default: + } + return '$$i.resolve("$v")'; + case EVar(n, t, e): + n = declLocal(n); + return e == null ? 'let $n' : 'let $n = ${exprValue(e)}'; + case EParent(e): + return '(${exprValue(e)})'; + case EBlock(el): + var old = localNames.copy(); + // pre define name functions + for( e in el ) + switch( Tools.expr(e) ) { + case EFunction(_,_,name,_): declLocal(name); + default: + } + var buf = new StringBuf(); + buf.add('{'); + for( e in el ) { + buf.add(exprJS(e)); + buf.add(";"); + } + buf.add('}'); + localNames = old; + return buf.toString(); + case EField(e,f) if( !isProperty(f) ): + return exprValue(e)+"."+f; + case EField(e, f): + return '$$i.get(${exprValue(e)},"$f")'; + case EBinop(op, e1, e2): + switch( op ) { + case "+","-","*","/","%","&","|","^",">>","<<",">>>","==","!=",">=","<=",">","<": + return '${exprOp(e1)} $op ${exprOp(e2)}'; + case "||","&&": + return '(${exprCond(e1)} $op ${exprCond(e2)})'; + case "=": + switch( Tools.expr(e1) ) { + case EIdent(id) if( localNames.exists(id) ): + return localNames.get(id)+" = "+exprValue(e2); + case EIdent(id) if( isContext(id) ): + return '$$i.ctx.$id = ${exprValue(e2)}'; + case EIdent(id): + return '$$i.setVar("$id",${exprValue(e2)})'; + case EField(e,f): + return addPos('$$i.set(${exprValue(e)},"$f")'); + case EArray(e, index): + return '$$i.setArray(${exprValue(e)},${exprValue(index)},${exprValue(e2)})'; + default: + error(EInvalidOp("=")); + } + case "+=","-=","*=","/=","%=","|=","&=","^=","<<=",">>=",">>>=": + var aop = op.substr(0, op.length - 1); + switch( Tools.expr(e1) ) { + case EIdent(id) if( localNames.exists(id) ): + return localNames.get(id)+" "+op+" "+exprValue(e2); + case EIdent(id) if( isContext(id) ): + return '$$i.ctx.$id $op ${exprValue(e2)}'; + case EIdent(id): + return '$$i.setVar("$id",$$i.resolve("$id") $aop (${exprValue(e2)}))'; + case EField(e, f): + return '(($$o,$$v) => $$i.set($$o,"$f",$$i.get($$o,"$f") $aop $$v)(${exprValue(e)},${exprValue(e2)})'; + case EArray(e, index): + return '(($$a,$$idx,$$v) => $$i.setArray($$a,$$idx,$$i.getArray($$a,$$idx) $aop $$v))(${exprValue(e)},${exprValue(index)},${exprValue(e2)})'; + default: + error(EInvalidOp(op)); + } + case "...": + return '$$i.intIterator(${exprValue(e1)},${exprValue(e2)})'; + case "is": + return '$$i.isOfType(${exprValue(e1)},${exprValue(e2)})'; + default: + error(EInvalidOp(op)); + } + case EUnop(op, prefix, e): + switch( op ) { + case "!": + return '${exprValue(e)} != true'; + case "-","~": + return op+exprOp(e); + case "++", "--": + switch( Tools.expr(e) ) { + case EIdent(id) if( localNames.exists(id) ): + id = localNames.get(id); + return prefix ? op + id : id + op; + case EIdent(id) if( isContext(id) ): + return prefix ? op + "$i.ctx."+id : "$i.ctx."+id + op; + case _ if( prefix ): + var one = Tools.mk(EConst(CInt(1)),e); + return exprJS(Tools.mk(EBinop(op.charAt(0)+"=",e,one),e)); + case EIdent(id): + var op = op.charAt(1); + return '(($$v) => ($$i.setVar("$id",$$v $op 1),$$v))($$i.resolve("$id"))'; + case EArray(e, index): + var op = op.charAt(0); + var v = declLocal("$v"); + var str = '(($$a,$$idx) => { let $v = $$i.getArray($$a,$$idx); $$i.setArray($$a,$$idx,$v $op 1); return $v; })(${exprValue(e)},${exprValue(index)})'; + localNames.remove(v); + return str; + case EField(e, f): + var v = declLocal("$v"); + var str = '(($$o) => { let $v = $$i.get($$o,"$f"); $$i.set($$o,"$f",$$v $op 1); return $v; })(${exprValue(e)})'; + localNames.remove(v); + return str; + default: + error(EInvalidOp(op)); + } + default: + error(EInvalidOp(op)); + } + case ECall(e, params): + var args = [for( p in params ) exprValue(p)]; + switch( Tools.expr(e) ) { + case EField(eobj,f): + var isCtx = false; + var obj = eobj; + while( true ) { + switch( Tools.expr(obj) ) { + case EField(o,_): obj = o; + case EIdent(i) if( isContext(i) ): isCtx = true; break; + default: break; + } + } + if( isCtx ) + return '${exprValue(e)}(${args.join(',')})'; + return addPos('$$i.fcall2(${exprValue(eobj)},"$f",[${args.join(',')}])'); + case EIdent(id) if( localNames.exists(id) ): + id = localNames.get(id); + return '$id(${args.join(',')})'; + case EIdent(id) if( isContext(id) ): + return '$$i.ctx.$id(${args.join(',')})'; + default: + return '$$i.call(null,${exprValue(e)},[${args.join(',')}])'; + } + case EIf(cond,e1,e2): + return 'if( ${exprCond(cond)} ) ${exprJS(e1)}'+(e2 == null ? "" : 'else ${exprJS(e2)}'); + case ETernary(cond, e1, e2): + return '(${exprCond(cond)} ? ${exprValue(e1)} : ${e2 == null ? 'undefined' : exprValue(e2)})'; + case EWhile(cond, e): + return 'while( ${exprValue(cond)} ) ${exprBreakContinue(e)}'; + case EDoWhile(cond, e): + return 'do ${exprBreakContinue(e)} while( ${exprCond(cond)} )'; + case EFor(v, it, e): + v = declLocal(v); + var block = exprBreakContinue(e,false); + localNames.remove(v); + var iter = '$$i.makeIterator(${exprValue(it)})'; + var it = declLocal("$it"); + var str = '{ let $it = ${addPos(iter)}; while( $it.hasNext() ) { let $v = $it.next(); $block; } }'; + localNames.remove(it); + return str; + case EBreak: + return 'break'; + case EContinue: + return 'continue'; + case EReturn(null): + return 'return'; + case EReturn(e): + return 'return '+exprValue(e); + case EArray(e, index): + return '$$i.getArray(${exprValue(e)},${exprValue(index)})'; + case EArrayDecl(arr): + if( arr.length > 0 && Tools.expr(arr[0]).match(EBinop("=>", _)) ) { + var keys = [], values = []; + for( e in arr ) { + switch(Tools.expr(e)) { + case EBinop("=>", eKey, eValue): + keys.push(exprValue(eKey)); + values.push(exprValue(eValue)); + default: + #if hscriptPos + curExpr = e; + #end + error(ECustom("Invalid map key=>value expression")); + } + } + return '$$i.makeMap([${keys.join(',')}],[${values.join(',')}])'; + } + return '['+[for( e in arr ) exprValue(e)].join(',')+']'; + case ENew(cl, params): + var args = [for( e in params ) exprValue(e)]; + return '$$i.cnew("$cl",[${args.join(',')}])'; + case EThrow(e): + return "throw "+exprValue(e); + case ETry(e, v, t, ecatch): + v = declLocal(v); + var ec = exprBlock(ecatch); + localNames.remove(v); + return 'try ${exprBlock(e)} catch( $v ) $ec'; + case EObject(fl): + var fields = [for( f in fl ) f.name+":"+exprValue(f.e)]; + return '{${fields.join(',')}}'; // do not use 'set' here + case EMeta(_, _, e), ECheckType(e,_): + return exprJS(e); + case EFunction(args, e, name, ret): + var prev = localNames.copy(); + if( name != null && !localNames.exists(name) ) + declLocal(name); + for( a in args ) + localNames.set(a.name, a.name); + var prevReturn = hasReturn; + hasReturn = false; + var bl = exprBlock(e); + if( hasReturn ) + bl = '{ try { $bl } catch( $$e ) { if( $$e.$$ == 1 ) return $$e.$$val; throw $$e; }}'; + hasReturn = prevReturn; + localNames = prev; + var fstr = 'function(${[for( a in args ) a.name].join(",")}) $bl'; + if( name != null ) + fstr = 'let $name = $$i.setVar("$name",$fstr)'; + return fstr; + case ESwitch(e, cases, defaultExpr): + var checks = [for( c in cases ) 'if( ${[for( v in c.values ) '$$v == ${exprValue(handleRBC(v))}'].join(" || ")} ) return ${exprValue(handleRBC(c.expr))};']; + if( defaultExpr != null ) + checks.push('return '+exprValue(handleRBC(defaultExpr))); + return '(($$v) => { ${[for( c in checks ) c+";"].join(" ")} })(${exprValue(e)})'; + default: + throw "TODO"; + } + } + + function fcall2( o : Dynamic, f : String, args : Array ) : Dynamic { + if( o == null ) { + error(EInvalidAccess(f)); + return null; + } + return fcall(o,f,args); + } + + function getArray( arr : Dynamic, index : Dynamic ) { + return isMap(arr) ? getMapValue(arr,index) : arr[index]; + } + + function setArray( arr : Dynamic, index : Dynamic, v : Dynamic ) { + if(isMap(arr) ) + setMapValue(arr, index, v); + else + arr[index] = v; + return v; + } + + function intIterator(v1,v2) { + return new IntIterator(v1,v2); + } + + function isOfType(v1,v2) { + return Std.isOfType(v1,v2); + } + + #if hscriptPos + function _p( pmin, pmax, origin, line ) { + curExpr = { e : null, pmin : pmin, pmax: pmax, origin:origin, line:line }; + } + #end + +} \ No newline at end of file diff --git a/lib/hscript/hscript/Macro.hx b/lib/hscript/hscript/Macro.hx new file mode 100644 index 00000000..5e1c45c9 --- /dev/null +++ b/lib/hscript/hscript/Macro.hx @@ -0,0 +1,262 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr.Error; +#if hscriptPos +import hscript.Expr.ErrorDef; +#end +import haxe.macro.Expr; + +class Macro { + + var p : Position; + var binops : Map; + var unops : Map; + + public function new(pos) { + p = pos; + binops = new Map(); + unops = new Map(); + for( c in Type.getEnumConstructs(Binop) ) { + if( c == "OpAssignOp" ) continue; + var op = Type.createEnum(Binop, c); + var assign = false; + var str = switch( op ) { + case OpAdd: assign = true; "+"; + case OpMult: assign = true; "*"; + case OpDiv: assign = true; "/"; + case OpSub: assign = true; "-"; + case OpAssign: "="; + case OpEq: "=="; + case OpNotEq: "!="; + case OpGt: ">"; + case OpGte: ">="; + case OpLt: "<"; + case OpLte: "<="; + case OpAnd: assign = true; "&"; + case OpOr: assign = true; "|"; + case OpXor: assign = true; "^"; + case OpBoolAnd: "&&"; + case OpBoolOr: "||"; + case OpShl: assign = true; "<<"; + case OpShr: assign = true; ">>"; + case OpUShr: assign = true; ">>>"; + case OpMod: assign = true; "%"; + case OpAssignOp(_): ""; + case OpInterval: "..."; + case OpArrow: "=>"; + #if (haxe_ver >= 4) + case OpIn: "in"; + #end + default: + continue; + }; + binops.set(str, op); + if( assign ) + binops.set(str + "=", OpAssignOp(op)); + } + for( c in Type.getEnumConstructs(Unop) ) { + var op = Type.createEnum(Unop, c); + var str = switch( op ) { + case OpNot: "!"; + case OpNeg: "-"; + case OpNegBits: "~"; + case OpIncrement: "++"; + case OpDecrement: "--"; + #if (haxe_ver >= 4.2) + case OpSpread: continue; + #end + } + unops.set(str, op); + } + } + + function map( a : Array, f : T -> R ) : Array { + var b = new Array(); + for( x in a ) + b.push(f(x)); + return b; + } + + function convertType( t : Expr.CType ) : ComplexType { + return switch( t ) { + case CTOpt(t): TOptional(convertType(t)); + case CTPath(pack, args): + var params = []; + if( args != null ) { + for( t in args ) + params.push(switch( t ) { + case CTExpr(e): TPExpr(convert(e)); + default: TPType(convertType(t)); + }); + } + var pack = pack.copy(); + TPath({ + pack : pack, + name : pack.pop(), + params : params, + sub : null, + }); + case CTParent(t): TParent(convertType(t)); + case CTFun(args, ret): + TFunction(map(args,convertType), convertType(ret)); + case CTNamed(name, convertType(_) => ct): + #if (haxe_ver >= 4) + TNamed(name, ct); + #else + ct; + #end + case CTAnon(fields): + var tf = []; + for( f in fields ) { + var meta = f.meta == null ? [] : [for( m in f.meta ) { name : m.name, params : m.params == null ? [] : [for( e in m.params ) convert(e)], pos : p }]; + tf.push( { name : f.name, meta : meta, doc : null, access : [], kind : FVar(convertType(f.t), null), pos : p } ); + } + TAnonymous(tf); + case CTExpr(_): + throw "assert"; + }; + } + + public function convert( e : hscript.Expr ) : Expr { + return { expr : switch( #if hscriptPos e.e #else e #end ) { + case EConst(c): + EConst(switch(c) { + case CInt(v): CInt(Std.string(v)); + case CFloat(f): CFloat(Std.string(f)); + case CString(s): CString(s); + }); + case EIdent(v): + EConst(CIdent(v)); + case EVar(n, t, e): + EVars([ { name : n, expr : if( e == null ) null else convert(e), type : if( t == null ) null else convertType(t) } ]); + case EParent(e): + EParenthesis(convert(e)); + case EBlock(el): + EBlock(map(el,convert)); + case EField(e, f): + EField(convert(e), f); + case EBinop(op, e1, e2): + var b = binops.get(op); + if( b == null ) throw EInvalidOp(op); + EBinop(b, convert(e1), convert(e2)); + case EUnop(op, prefix, e): + var u = unops.get(op); + if( u == null ) throw EInvalidOp(op); + EUnop(u, !prefix, convert(e)); + case ECall(e, params): + ECall(convert(e), map(params, convert)); + case EIf(c, e1, e2): + EIf(convert(c), convert(e1), e2 == null ? null : convert(e2)); + case EWhile(c, e): + EWhile(convert(c), convert(e), true); + case EDoWhile(c, e): + EWhile(convert(c), convert(e), false); + case EFor(v, it, efor): + var p = #if (!macro && hscriptPos) { file : p.file, min : e.pmin, max : e.pmax } #else p #end; + EFor({ expr : EBinop(OpIn,{ expr : EConst(CIdent(v)), pos : p },convert(it)), pos : p }, convert(efor)); + case EForGen(it, efor): + EFor(convert(it), convert(efor)); + case EBreak: + EBreak; + case EContinue: + EContinue; + case EFunction(args, e, name, ret): + var targs = []; + for( a in args ) + targs.push( { + name : a.name, + type : a.t == null ? null : convertType(a.t), + opt : false, + value : null, + }); + EFunction(#if haxe4 name != null ? FNamed(name,false) : FAnonymous #else name #end, { + params : [], + args : targs, + expr : convert(e), + ret : ret == null ? null : convertType(ret), + }); + case EReturn(e): + EReturn(e == null ? null : convert(e)); + case EArray(e, index): + EArray(convert(e), convert(index)); + case EArrayDecl(el): + EArrayDecl(map(el,convert)); + case ENew(cl, params): + var pack = cl.split("."); + ENew( { pack : pack, name : pack.pop(), params : [], sub : null }, map(params, convert)); + case EThrow(e): + EThrow(convert(e)); + case ETry(e, v, t, ec): + ETry(convert(e), [ { type : convertType(t), name : v, expr : convert(ec) } ]); + case EObject(fields): + var tf = []; + for( f in fields ) + tf.push( { field : f.name, expr : convert(f.e) } ); + EObjectDecl(tf); + case ETernary(cond, e1, e2): + ETernary(convert(cond), convert(e1), convert(e2)); + case ESwitch(e, cases, edef): + ESwitch(convert(e), [for( c in cases ) { values : [for( v in c.values ) convert(v)], expr : convert(c.expr) } ], edef == null ? null : convert(edef)); + case EMeta(m, params, esub): + var mpos = #if (!macro && hscriptPos) { file : p.file, min : e.pmin, max : e.pmax } #else p #end; + EMeta({ name : m, params : params == null ? [] : [for( p in params ) convert(p)], pos : mpos }, convert(esub)); + case ECheckType(e, t): + ECheckType(convert(e), convertType(t)); + case ECast(e, t): + ECast(convert(e), t == null ? null : convertType(t)); + }, pos : #if (!macro && hscriptPos) { file : p.file, min : e.pmin, max : e.pmax } #else p #end } + } + + public function typeEncode( t : ComplexType ) : Expr.CType { + switch( t ) { + case TPath(p): + var path = p.pack.copy(); + path.push(p.name); + if( p.sub != null ) path.push(p.sub); + var params : Array = null; + if( p.params != null && p.params.length > 0 ) + params = [for( p in p.params ) switch( p ) { + case TPType(t): typeEncode(t); + case TPExpr(e): CTExpr(null); // TODO : macro expr to hscript expr + }]; + return CTPath(path,params); + case TFunction(args, ret): + return CTFun([for( a in args ) typeEncode(a)], typeEncode(ret)); + case TAnonymous(fields): + return CTAnon([for( f in fields ) { name : f.name, t : switch( f.kind ) { + case FVar(t): typeEncode(t); + case FProp(get,set,t,_): typeEncode(t); + case FFun(f): CTFun([for( a in f.args ) typeEncode(a.type)],typeEncode(f.ret)); + }}]); + case TParent(t): + return CTParent(typeEncode(t)); + case TOptional(t): + return CTOpt(typeEncode(t)); + case TNamed(n, t): + return CTNamed(n,typeEncode(t)); + case TIntersection(_), TExtend(_): + throw "assert"; + } + } + +} diff --git a/lib/hscript/hscript/Parser.hx b/lib/hscript/hscript/Parser.hx new file mode 100644 index 00000000..723153e8 --- /dev/null +++ b/lib/hscript/hscript/Parser.hx @@ -0,0 +1,1801 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr; + +enum Token { + TEof; + TConst( c : Const ); + TId( s : String ); + TOp( s : String ); + TPOpen; + TPClose; + TBrOpen; + TBrClose; + TDot; + TQuestionDot; + TComma; + TSemicolon; + TBkOpen; + TBkClose; + TQuestion; + TDoubleDot; + TMeta( s : String ); + TPrepro( s : String ); +} + +class Parser { + + // config / variables + public var line : Int; + public var opChars : String; + public var identChars : String; + public var opPriority : Map; + public var opRightAssoc : Map; + + /** + allows to check for #if / #else in code + **/ + public var preprocesorValues : Map = new Map(); + + /** + activate JSON compatiblity + **/ + public var allowJSON : Bool; + + /** + allow types declarations + **/ + public var allowTypes : Bool; + + /** + allow haxe metadata declarations + **/ + public var allowMetadata : Bool; + + /** + resume from parsing errors (when parsing incomplete code, during completion for example) + **/ + public var resumeErrors : Bool; + + // implementation + var input : String; + var readPos : Int; + var offset : Int; + var currentPos(get,never) : Int; + + var char : Int; + var ops : Array; + var idents : Array; + var uid : Int = 0; + + #if hscriptPos + var origin : String; + var tokenMin : Int; + var tokenMax : Int; + var oldTokenMin : Int; + var oldTokenMax : Int; + var tokens : List<{ min : Int, max : Int, t : Token }>; + #else + static inline var p1 = 0; + static inline var tokenMin = 0; + static inline var tokenMax = 0; + var tokens : haxe.ds.GenericStack; + #end + + + public function new() { + line = 1; + opChars = "+*/-=!><&|^%~"; + identChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; + var priorities = [ + ["%"], + ["*", "/"], + ["+", "-"], + ["<<", ">>", ">>>"], + ["|", "&", "^"], + ["==", "!=", ">", "<", ">=", "<="], + ["..."], + ["&&"], + ["||"], + ["=","+=","-=","*=","/=","%=","<<=",">>=",">>>=","|=","&=","^=","=>"], + ["->"], + ["in","is"] + ]; + opPriority = new Map(); + opRightAssoc = new Map(); + for( i in 0...priorities.length ) + for( x in priorities[i] ) { + opPriority.set(x, i); + if( i == 9 ) opRightAssoc.set(x, true); + } + for( x in ["!", "++", "--", "~"] ) // unary "-" handled in parser directly! + opPriority.set(x, x == "++" || x == "--" ? -1 : -2); + } + + inline function get_currentPos() return readPos + offset; + + public inline function error( err, pmin, pmax ) { + if( !resumeErrors ) + #if hscriptPos + throw new Error(err, pmin, pmax, origin, line); + #else + throw err; + #end + } + + public function invalidChar(c) { + error(EInvalidChar(c), readPos-1, readPos-1); + } + + function initParser( origin, pos ) { + // line=1 - don't reset line : it might be set manualy + preprocStack = []; + #if hscriptPos + this.origin = origin; + readPos = 0; + tokenMin = oldTokenMin = pos; + tokenMax = oldTokenMax = pos; + tokens = new List(); + #else + tokens = new haxe.ds.GenericStack(); + #end + offset = pos; + char = -1; + ops = new Array(); + idents = new Array(); + uid = 0; + for( i in 0...opChars.length ) + ops[opChars.charCodeAt(i)] = true; + for( i in 0...identChars.length ) + idents[identChars.charCodeAt(i)] = true; + } + + public function parseString( s : String, ?origin : String = "hscript", ?position : Int = 0 ) { + initParser(origin, position); + input = s; + readPos = 0; + var a = new Array(); + while( true ) { + var tk = token(); + if( tk == TEof ) break; + push(tk); + parseFullExpr(a); + } + return if( a.length == 1 ) a[0] else mk(EBlock(a),0); + } + + function unexpected( tk ) : Dynamic { + error(EUnexpected(tokenString(tk)),tokenMin,tokenMax); + return null; + } + + inline function push(tk) { + #if hscriptPos + tokens.push( { t : tk, min : tokenMin, max : tokenMax } ); + tokenMin = oldTokenMin; + tokenMax = oldTokenMax; + #else + tokens.add(tk); + #end + } + + inline function ensure(tk) { + var t = token(); + if( t != tk ) unexpected(t); + } + + inline function ensureToken(tk) { + var t = token(); + if( !Type.enumEq(t,tk) ) unexpected(t); + } + + function maybe(tk) { + var t = token(); + if( Type.enumEq(t, tk) ) + return true; + push(t); + return false; + } + + function getIdent() { + var tk = token(); + switch( tk ) { + case TId(id): return id; + default: + unexpected(tk); + return null; + } + } + + inline function expr(e:Expr) { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + inline function pmin(e:Expr) { + #if hscriptPos + return e == null ? 0 : e.pmin; + #else + return 0; + #end + } + + inline function pmax(e:Expr) { + #if hscriptPos + return e == null ? 0 : e.pmax; + #else + return 0; + #end + } + + inline function mk(e,pmin=-1,pmax=-1) : Expr { + #if hscriptPos + if( e == null ) return null; + if( pmin < 0 ) pmin = tokenMin; + if( pmax < 0 ) pmax = tokenMax; + return { e : e, pmin : pmin, pmax : pmax, origin : origin, line : line }; + #else + return e; + #end + } + + function isBlock(e) { + if( e == null ) return false; + return switch( expr(e) ) { + case EBlock(_), EObject(_), ESwitch(_): true; + case EFunction(_,e,_,_): isBlock(e); + case EVar(_, t, e): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false; + case EIf(_,e1,e2): if( e2 != null ) isBlock(e2) else isBlock(e1); + case EBinop(_,_,e): isBlock(e); + case EUnop(_,prefix,e): !prefix && isBlock(e); + case EWhile(_,e): isBlock(e); + case EDoWhile(_,e): isBlock(e); + case EFor(_,_,e), EForGen(_, e): isBlock(e); + case EReturn(e): e != null && isBlock(e); + case ETry(_, _, _, e): isBlock(e); + case EMeta(_, _, e): isBlock(e); + default: false; + } + } + + function parseFullExpr( exprs : Array ) { + var e = parseExpr(); + exprs.push(e); + + var tk = token(); + // this is a hack to support var a,b,c; with a single EVar + while( tk == TComma && e != null && expr(e).match(EVar(_)) ) { + e = parseStructure("var"); // next variable + exprs.push(e); + tk = token(); + } + + if( tk != TSemicolon && tk != TEof ) { + if( isBlock(e) ) + push(tk); + else + unexpected(tk); + } + } + + function parseObject(p1) { + // parse object + var fl = new Array(); + while( true ) { + var tk = token(); + var id = null; + switch( tk ) { + case TId(i): id = i; + case TConst(c): + if( !allowJSON ) + unexpected(tk); + switch( c ) { + case CString(s): id = s; + default: unexpected(tk); + } + case TBrClose: + break; + default: + unexpected(tk); + break; + } + ensure(TDoubleDot); + fl.push({ name : id, e : parseExpr() }); + tk = token(); + switch( tk ) { + case TBrClose: + break; + case TComma: + default: + unexpected(tk); + } + } + return parseExprNext(mk(EObject(fl),p1)); + } + + function parseExpr() { + var tk = token(); + #if hscriptPos + var p1 = tokenMin; + #end + switch( tk ) { + case TId(id): + var e = parseStructure(id); + if( e == null ) + e = mk(EIdent(id)); + return parseExprNext(e); + case TConst(c): + return parseExprNext(mk(EConst(c))); + case TPOpen: + tk = token(); + if( tk == TPClose ) { + ensureToken(TOp("->")); + var eret = parseExpr(); + return mkLambda([],eret,p1); + } + push(tk); + var e = parseExpr(); + tk = token(); + switch( tk ) { + case TPClose: + return parseExprNext(mk(EParent(e),p1,tokenMax)); + case TDoubleDot: + var t = parseType(); + tk = token(); + switch( tk ) { + case TPClose: + return parseExprNext(mk(ECheckType(e,t),p1,tokenMax)); + case TComma: + switch( expr(e) ) { + case EIdent(v): return parseLambda([{ name : v, t : t }], pmin(e)); + default: + } + default: + } + case TComma: + switch( expr(e) ) { + case EIdent(v): return parseLambda([{name:v}], pmin(e)); + default: + } + default: + } + return unexpected(tk); + case TBrOpen: + tk = token(); + switch( tk ) { + case TBrClose: + return parseExprNext(mk(EObject([]),p1)); + case TId(_): + var tk2 = token(); + push(tk2); + push(tk); + switch( tk2 ) { + case TDoubleDot: + return parseExprNext(parseObject(p1)); + default: + } + case TConst(c): + if( allowJSON ) { + switch( c ) { + case CString(_): + var tk2 = token(); + push(tk2); + push(tk); + switch( tk2 ) { + case TDoubleDot: + return parseExprNext(parseObject(p1)); + default: + } + default: + push(tk); + } + } else + push(tk); + default: + push(tk); + } + var a = new Array(); + while( true ) { + parseFullExpr(a); + tk = token(); + if( tk == TBrClose || (resumeErrors && tk == TEof) ) + break; + push(tk); + } + return mk(EBlock(a),p1); + case TOp(op): + if( op == "-" ) { + var start = tokenMin; + var e = parseExpr(); + if( e == null ) + return makeUnop(op,e); + switch( expr(e) ) { + case EConst(CInt(i)): + return mk(EConst(CInt(-i)), start, pmax(e)); + case EConst(CFloat(f)): + return mk(EConst(CFloat(-f)), start, pmax(e)); + default: + return makeUnop(op,e); + } + } + if( opPriority.get(op) < 0 ) + return makeUnop(op,parseExpr()); + if( op == "<" ) { + var start = readPos - 1; + var ident = getIdent(); + if( #if hscriptPos tokens.length != 0 #else !tokens.isEmpty() #end ) + throw "assert"; + if( readPos == start + ident.length + 1 ) { + var endTag = ""; + var end = input.indexOf(endTag, readPos); + if( end < 0 ) { + endTag = '/>'; + end = input.indexOf(endTag, readPos); + } + if( end >= 0 ) { + readPos = end + endTag.length; + char = -1; + start--; + var end = readPos - 1; + #if hscriptPos + tokenMin = start + offset; + tokenMax = end + offset; + #end + var str = input.substr(start,end - start + 1); + return mk(EMeta(":markup",[],mk(EConst(CString(str))))); + } + } + } + return unexpected(tk); + case TBkOpen: + var a = new Array(); + tk = token(); + var first = true; + while( tk != TBkClose && (!resumeErrors || tk != TEof) ) { + if (!first) { + if (tk != TComma) + unexpected(tk); + else { + tk = token(); + if (tk == TBkClose) + break; + } + } + first = false; + push(tk); + a.push(parseExpr()); + tk = token(); + } + if( a.length == 1 && a[0] != null ) + switch( expr(a[0]) ) { + case EFor(_), EWhile(_), EDoWhile(_): + var tmp = "__a_" + (uid++); + var e = mk(EBlock([ + mk(EVar(tmp, null, mk(EArrayDecl([]), p1)), p1), + mapCompr(tmp, a[0]), + mk(EIdent(tmp),p1), + ]),p1); + return parseExprNext(e); + default: + } + return parseExprNext(mk(EArrayDecl(a), p1)); + case TMeta(id) if( allowMetadata ): + var args = parseMetaArgs(); + return mk(EMeta(id, args, parseExpr()),p1); + default: + return unexpected(tk); + } + } + + function parseLambda( args : Array, pmin ) { + while( true ) { + var id = getIdent(); + var t = maybe(TDoubleDot) ? parseType() : null; + args.push({ name : id, t : t }); + var tk = token(); + switch( tk ) { + case TComma: + case TPClose: + break; + default: + unexpected(tk); + break; + } + } + ensureToken(TOp("->")); + var eret = parseExpr(); + return mkLambda(args,eret,pmin); + } + + function mkLambda(args,eret,p) { + return mk(EFunction(args, mk(EMeta(":lambda",[],mk(EReturn(eret),pmin(eret))),p)),p); + } + + function parseMetaArgs() { + var tk = token(); + if( tk != TPOpen ) { + push(tk); + return null; + } + var args = []; + tk = token(); + if( tk != TPClose ) { + push(tk); + while( true ) { + args.push(parseExpr()); + switch( token() ) { + case TComma: + case TPClose: + break; + case tk: + unexpected(tk); + } + } + } + return args; + } + + function mapCompr( tmp : String, e : Expr ) { + if( e == null ) return null; + var edef = switch( expr(e) ) { + case EFor(v, it, e2): + EFor(v, it, mapCompr(tmp, e2)); + case EForGen(it, e2): + EForGen(it, mapCompr(tmp, e2)); + case EWhile(cond, e2): + EWhile(cond, mapCompr(tmp, e2)); + case EDoWhile(cond, e2): + EDoWhile(cond, mapCompr(tmp, e2)); + case EIf(cond, e1, e2) if( e2 == null ): + EIf(cond, mapCompr(tmp, e1), null); + case EBlock([e]): + EBlock([mapCompr(tmp, e)]); + case EParent(e2): + EParent(mapCompr(tmp, e2)); + default: + ECall( mk(EField(mk(EIdent(tmp), pmin(e), pmax(e)), "push"), pmin(e), pmax(e)), [e]); + } + return mk(edef, pmin(e), pmax(e)); + } + + function makeUnop( op, e ) { + if( e == null && resumeErrors ) + return null; + return switch( expr(e) ) { + case EBinop(bop, e1, e2): mk(EBinop(bop, makeUnop(op, e1), e2), pmin(e1), pmax(e2)); + case ETernary(e1, e2, e3): mk(ETernary(makeUnop(op, e1), e2, e3), pmin(e1), pmax(e3)); + default: mk(EUnop(op,true,e),pmin(e),pmax(e)); + } + } + + function makeBinop( op, e1, e ) { + if( e == null && resumeErrors ) + return mk(EBinop(op,e1,e),pmin(e1),pmax(e1)); + return switch( expr(e) ) { + case EBinop(op2,e2,e3): + var delta = opPriority.get(op) - opPriority.get(op2); + if( delta < 0 || (delta == 0 && !opRightAssoc.exists(op)) ) + mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3)); + else + mk(EBinop(op, e1, e), pmin(e1), pmax(e)); + case ETernary(e2,e3,e4): + if( opRightAssoc.exists(op) ) + mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + else + mk(ETernary(makeBinop(op, e1, e2), e3, e4), pmin(e1), pmax(e)); + default: + mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + } + } + + function parseStructure(id) { + #if hscriptPos + var p1 = tokenMin; + #end + return switch( id ) { + case "if": + ensure(TPOpen); + var cond = parseExpr(); + ensure(TPClose); + var e1 = parseExpr(); + var e2 = null; + var semic = false; + var tk = token(); + if( tk == TSemicolon ) { + semic = true; + tk = token(); + } + if( Type.enumEq(tk,TId("else")) ) + e2 = parseExpr(); + else { + push(tk); + if( semic ) push(TSemicolon); + } + mk(EIf(cond,e1,e2),p1,(e2 == null) ? tokenMax : pmax(e2)); + case "var", "final": + var ident = getIdent(); + var tk = token(); + var t = null; + if( tk == TDoubleDot && allowTypes ) { + t = parseType(); + tk = token(); + } + var e = null; + + switch (tk) + { + case TOp("="): e = parseExpr(); + case TOp(_): unexpected(tk); + case TComma | TSemicolon: push(tk); + // Above case should be enough but semicolon is not mandatory after } + case _ if (t != null): push(tk); + default: unexpected(tk); + } + + mk(EVar(ident,t,e),p1,(e == null) ? tokenMax : pmax(e)); + case "while": + var econd = parseExpr(); + var e = parseExpr(); + mk(EWhile(econd,e),p1,pmax(e)); + case "do": + var e = parseExpr(); + var tk = token(); + switch(tk) + { + case TId("while"): // Valid + default: unexpected(tk); + } + var econd = parseExpr(); + mk(EDoWhile(econd,e),p1,pmax(econd)); + case "for": + ensure(TPOpen); + var eit = parseExpr(); + ensure(TPClose); + var e = parseExpr(); + switch( expr(eit) ) { + case EBinop("in",ev,eit): + switch( expr(ev) ) { + case EIdent(v): + return mk(EFor(v,eit,e),p1,pmax(e)); + default: + } + default: + } + mk(EForGen(eit,e),p1,pmax(e)); + case "break": mk(EBreak); + case "continue": mk(EContinue); + case "else": unexpected(TId(id)); + case "inline": + if( !maybe(TId("function")) ) unexpected(TId("inline")); + return parseStructure("function"); + case "function": + var tk = token(); + var name = null; + switch( tk ) { + case TId(id): name = id; + default: push(tk); + } + var inf = parseFunctionDecl(); + mk(EFunction(inf.args, inf.body, name, inf.ret),p1,pmax(inf.body)); + case "return": + var tk = token(); + push(tk); + var e = if( tk == TSemicolon ) null else parseExpr(); + mk(EReturn(e),p1,if( e == null ) tokenMax else pmax(e)); + case "new": + var a = new Array(); + a.push(getIdent()); + while( true ) { + var tk = token(); + switch( tk ) { + case TDot: + a.push(getIdent()); + case TPOpen: + break; + default: + unexpected(tk); + break; + } + } + var args = parseExprList(TPClose); + mk(ENew(a.join("."),args),p1); + case "throw": + var e = parseExpr(); + mk(EThrow(e),p1,pmax(e)); + case "try": + var e = parseExpr(); + ensureToken(TId("catch")); + ensure(TPOpen); + var vname = getIdent(); + ensure(TDoubleDot); + var t = null; + if( allowTypes ) + t = parseType(); + else + ensureToken(TId("Dynamic")); + ensure(TPClose); + var ec = parseExpr(); + mk(ETry(e, vname, t, ec), p1, pmax(ec)); + case "switch": + var e = parseExpr(); + var def = null, cases = []; + ensure(TBrOpen); + while( true ) { + var tk = token(); + switch( tk ) { + case TId("case"): + var c = { values : [], expr : null }; + cases.push(c); + while( true ) { + var e = parseExpr(); + c.values.push(e); + tk = token(); + switch( tk ) { + case TComma: + // next expr + case TDoubleDot: + break; + default: + unexpected(tk); + break; + } + } + var exprs = []; + while( true ) { + tk = token(); + push(tk); + switch( tk ) { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if( resumeErrors ): + break; + default: + parseFullExpr(exprs); + } + } + c.expr = if( exprs.length == 1) + exprs[0]; + else if( exprs.length == 0 ) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + case TId("default"): + if( def != null ) unexpected(tk); + ensure(TDoubleDot); + var exprs = []; + while( true ) { + tk = token(); + push(tk); + switch( tk ) { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if( resumeErrors ): + break; + default: + parseFullExpr(exprs); + } + } + def = if( exprs.length == 1) + exprs[0]; + else if( exprs.length == 0 ) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + case TBrClose: + break; + default: + unexpected(tk); + break; + } + } + mk(ESwitch(e, cases, def), p1, tokenMax); + case "cast": + var tk = token(); + if( tk == TPOpen ) { + var e = parseExpr(); + ensure(TComma); + var t = parseType(); + mk(ECast(e,t), p1, tokenMax); + } else { + push(tk); + var e = parseExpr(); + mk(ECast(e,null), p1, tokenMax); + } + default: + null; + } + } + + function parseExprNext( e1 : Expr ) { + var tk = token(); + switch( tk ) { + case TOp(op): + + if( op == "->" ) { + // single arg reinterpretation of `f -> e` , `(f) -> e` and `(f:T) -> e` + switch( expr(e1) ) { + case EIdent(i), EParent(expr(_) => EIdent(i)): + var eret = parseExpr(); + return mkLambda([{ name : i }], eret, pmin(e1)); + case ECheckType(expr(_) => EIdent(i), t): + var eret = parseExpr(); + return mkLambda([{ name : i, t : t }], eret, pmin(e1)); + default: + } + unexpected(tk); + } + + if( opPriority.get(op) == -1 ) { + if( isBlock(e1) || switch(expr(e1)) { case EParent(_): true; default: false; } ) { + push(tk); + return e1; + } + return parseExprNext(mk(EUnop(op,false,e1),pmin(e1))); + } + return makeBinop(op,e1,parseExpr()); + case TId(op) if( opPriority.exists(op) ): + return parseExprNext(makeBinop(op,e1,parseExpr())); + case TDot: + var field = getIdent(); + return parseExprNext(mk(EField(e1,field),pmin(e1))); + case TQuestionDot: + var field = getIdent(); + var tmp = "__a_" + (uid++); + var e = mk(EBlock([ + mk(EVar(tmp, null, e1), pmin(e1), pmax(e1)), + mk(ETernary( + mk(EBinop("==", mk(EIdent(tmp),pmin(e1),pmax(e1)), mk(EIdent("null"),pmin(e1),pmax(e1)))), + mk(EIdent("null"),pmin(e1),pmax(e1)), + mk(EField(mk(EIdent(tmp),pmin(e1),pmax(e1)),field),pmin(e1)) + )) + ]),pmin(e1)); + return parseExprNext(e); + case TPOpen: + return parseExprNext(mk(ECall(e1,parseExprList(TPClose)),pmin(e1))); + case TBkOpen: + var e2 = parseExpr(); + ensure(TBkClose); + return parseExprNext(mk(EArray(e1,e2),pmin(e1))); + case TQuestion: + var e2 = parseExpr(); + ensure(TDoubleDot); + var e3 = parseExpr(); + return mk(ETernary(e1,e2,e3),pmin(e1),pmax(e3)); + default: + push(tk); + return e1; + } + } + + function parseFunctionArgs() { + var args = new Array(); + var tk = token(); + if( tk != TPClose ) { + var done = false; + while( !done ) { + var name = null, opt = false; + switch( tk ) { + case TQuestion: + opt = true; + tk = token(); + default: + } + switch( tk ) { + case TId(id): name = id; + default: + unexpected(tk); + break; + } + var arg : Argument = { name : name }; + args.push(arg); + if( opt ) arg.opt = true; + if( allowTypes ) { + if( maybe(TDoubleDot) ) + arg.t = parseType(); + if( maybe(TOp("=")) ) + arg.value = parseExpr(); + } + tk = token(); + switch( tk ) { + case TComma: + tk = token(); + case TPClose: + done = true; + default: + unexpected(tk); + } + } + } + return args; + } + + function parseFunctionDecl() { + ensure(TPOpen); + var args = parseFunctionArgs(); + var ret = null; + if( allowTypes ) { + var tk = token(); + if( tk != TDoubleDot ) + push(tk); + else + ret = parseType(); + } + return { args : args, ret : ret, body : parseExpr() }; + } + + function parsePath() { + var path = [getIdent()]; + while( true ) { + var t = token(); + if( t != TDot ) { + push(t); + break; + } + path.push(getIdent()); + } + return path; + } + + function parseType() : CType { + var t = token(); + switch( t ) { + case TId(v): + push(t); + var path = parsePath(); + var params = null; + t = token(); + switch( t ) { + case TOp(op): + if( op == "<" ) { + params = []; + while( true ) { + switch( token() ) { + case TConst(c): + params.push(CTExpr(mk(EConst(c)))); + case tk: + push(tk); + params.push(parseType()); + } + t = token(); + switch( t ) { + case TComma: continue; + case TOp(op): + if( op == ">" ) break; + if( op.charCodeAt(0) == ">".code ) { + #if hscriptPos + tokens.add({ t : TOp(op.substr(1)), min : tokenMax - op.length - 1, max : tokenMax }); + #else + tokens.add(TOp(op.substr(1))); + #end + break; + } + default: + } + unexpected(t); + break; + } + } else + push(t); + default: + push(t); + } + return parseTypeNext(CTPath(path, params)); + case TPOpen: + var a = token(); + var b = token(); + + push(b); + push(a); + + function withReturn(args) { + switch token() { // I think it wouldn't hurt if ensure used enumEq + case TOp('->'): + case t: unexpected(t); + } + + return CTFun(args, parseType()); + } + + switch [a, b] { + case [TPClose, _] | [TId(_), TDoubleDot]: + + var args = [for (arg in parseFunctionArgs()) { + switch arg.value { + case null: + case v: + error(ECustom('Default values not allowed in function types'), #if hscriptPos v.pmin, v.pmax #else 0, 0 #end); + } + + CTNamed(arg.name, if (arg.opt) CTOpt(arg.t) else arg.t); + }]; + + return withReturn(args); + default: + + var t = parseType(); + return switch token() { + case TComma: + var args = [t]; + + while (true) { + args.push(parseType()); + if (!maybe(TComma)) break; + } + ensure(TPClose); + withReturn(args); + case TPClose: + parseTypeNext(CTParent(t)); + case t: unexpected(t); + } + } + case TBrOpen: + var fields = []; + var meta = null; + while( true ) { + t = token(); + switch( t ) { + case TBrClose: break; + case TId("var"), TId("final"): + var name = getIdent(); + ensure(TDoubleDot); + if( t.match(TId("final")) ) { + if( meta == null ) meta = []; + meta.push({ name : ":final", params : [] }); + } + fields.push( { name : name, t : parseType(), meta : meta } ); + meta = null; + ensure(TSemicolon); + case TId(name): + ensure(TDoubleDot); + fields.push( { name : name, t : parseType(), meta : meta } ); + t = token(); + switch( t ) { + case TComma: + case TBrClose: break; + default: unexpected(t); + } + case TMeta(name): + if( meta == null ) meta = []; + meta.push({ name : name, params : parseMetaArgs() }); + default: + unexpected(t); + break; + } + } + return parseTypeNext(CTAnon(fields)); + default: + return unexpected(t); + } + } + + function parseTypeNext( t : CType ) { + var tk = token(); + switch( tk ) { + case TOp(op): + if( op != "->" ) { + push(tk); + return t; + } + default: + push(tk); + return t; + } + var t2 = parseType(); + switch( t2 ) { + case CTFun(args, _): + args.unshift(t); + return t2; + default: + return CTFun([t], t2); + } + } + + function parseExprList( etk ) { + var args = new Array(); + var tk = token(); + if( tk == etk ) + return args; + push(tk); + while( true ) { + args.push(parseExpr()); + tk = token(); + switch( tk ) { + case TComma: + default: + if( tk == etk ) break; + unexpected(tk); + break; + } + } + return args; + } + + // ------------------------ module ------------------------------- + + public function parseModule( content : String, ?origin : String = "hscript", ?position = 0 ) { + initParser(origin, position); + input = content; + readPos = 0; + allowTypes = true; + allowMetadata = true; + var decls = []; + while( true ) { + var tk = token(); + if( tk == TEof ) break; + push(tk); + decls.push(parseModuleDecl()); + } + return decls; + } + + function parseMetadata() : Metadata { + var meta = []; + while( true ) { + var tk = token(); + switch( tk ) { + case TMeta(name): + meta.push({ name : name, params : parseMetaArgs() }); + default: + push(tk); + break; + } + } + return meta; + } + + function parseParams() { + if( maybe(TOp("<")) ) + error(EInvalidOp("Unsupported class type parameters"), currentPos, currentPos); + return {}; + } + + function parseModuleDecl() : ModuleDecl { + var meta = parseMetadata(); + var ident = getIdent(); + var isPrivate = false, isExtern = false; + while( true ) { + switch( ident ) { + case "private": + isPrivate = true; + case "extern": + isExtern = true; + default: + break; + } + ident = getIdent(); + } + switch( ident ) { + case "package": + var path = parsePath(); + ensure(TSemicolon); + return DPackage(path); + case "import": + var path = [getIdent()]; + var star = false; + while( true ) { + var t = token(); + if( t != TDot ) { + push(t); + break; + } + t = token(); + switch( t ) { + case TId(id): + path.push(id); + case TOp("*"): + star = true; + default: + unexpected(t); + } + } + var name = null; + if ( maybe(TId("as")) && !star) { + var t = token(); + switch( t ) { + case TId(id): + name = id; + default: + unexpected(t); + } + } + ensure(TSemicolon); + return DImport(path, star, name); + case "class": + var name = getIdent(); + var params = parseParams(); + var extend = null; + var implement = []; + + while( true ) { + var t = token(); + switch( t ) { + case TId("extends"): + extend = parseType(); + case TId("implements"): + implement.push(parseType()); + default: + push(t); + break; + } + } + + var fields = []; + ensure(TBrOpen); + while( !maybe(TBrClose) ) + fields.push(parseField()); + + return DClass({ + name : name, + meta : meta, + params : params, + extend : extend, + implement : implement, + fields : fields, + isPrivate : isPrivate, + isExtern : isExtern, + }); + case "typedef": + var name = getIdent(); + var params = parseParams(); + ensureToken(TOp("=")); + var t = parseType(); + return DTypedef({ + name : name, + meta : meta, + params : params, + isPrivate : isPrivate, + t : t, + }); + default: + unexpected(TId(ident)); + } + return null; + } + + function parseField() : FieldDecl { + var meta = parseMetadata(); + var access = []; + while( true ) { + var id = getIdent(); + switch( id ) { + case "override": + access.push(AOverride); + case "public": + access.push(APublic); + case "private": + access.push(APrivate); + case "inline": + access.push(AInline); + case "static": + access.push(AStatic); + case "macro": + access.push(AMacro); + case "function": + var name = getIdent(); + var inf = parseFunctionDecl(); + return { + name : name, + meta : meta, + access : access, + kind : KFunction({ + args : inf.args, + expr : inf.body, + ret : inf.ret, + }), + }; + case "var", "final": + var name = getIdent(); + var get = null, set = null; + if( maybe(TPOpen) ) { + get = getIdent(); + ensure(TComma); + set = getIdent(); + ensure(TPClose); + } + var type = maybe(TDoubleDot) ? parseType() : null; + var expr = maybe(TOp("=")) ? parseExpr() : null; + + if( expr != null ) { + if( isBlock(expr) ) + maybe(TSemicolon); + else + ensure(TSemicolon); + } else if( type != null && type.match(CTAnon(_)) ) { + maybe(TSemicolon); + } else + ensure(TSemicolon); + + return { + name : name, + meta : meta, + access : access, + kind : KVar({ + get : get, + set : set, + type : type, + expr : expr, + }), + }; + default: + unexpected(TId(id)); + break; + } + } + return null; + } + + // ------------------------ lexing ------------------------------- + + inline function readChar() { + return StringTools.fastCodeAt(input, readPos++); + } + + function readString( until ) { + var c = 0; + var b = new StringBuf(); + var esc = false; + var old = line; + var s = input; + #if hscriptPos + var p1 = currentPos - 1; + #end + while( true ) { + var c = readChar(); + if( StringTools.isEof(c) ) { + line = old; + error(EUnterminatedString, p1, p1); + break; + } + if( esc ) { + esc = false; + switch( c ) { + case 'n'.code: b.addChar('\n'.code); + case 'r'.code: b.addChar('\r'.code); + case 't'.code: b.addChar('\t'.code); + case "'".code, '"'.code, '\\'.code: b.addChar(c); + case '/'.code: if( allowJSON ) b.addChar(c) else invalidChar(c); + case "u".code: + if( !allowJSON ) invalidChar(c); + var k = 0; + for( i in 0...4 ) { + k <<= 4; + var char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: // 0-9 + k += char - 48; + case 65,66,67,68,69,70: // A-F + k += char - 55; + case 97,98,99,100,101,102: // a-f + k += char - 87; + default: + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedString, p1, p1); + } + invalidChar(char); + } + } + b.addChar(k); + default: invalidChar(c); + } + } else if( c == 92 ) + esc = true; + else if( c == until ) + break; + else { + if( c == 10 ) line++; + b.addChar(c); + } + } + return b.toString(); + } + + function token() { + #if hscriptPos + var t = tokens.pop(); + if( t != null ) { + tokenMin = t.min; + tokenMax = t.max; + return t.t; + } + oldTokenMin = tokenMin; + oldTokenMax = tokenMax; + tokenMin = (this.char < 0) ? currentPos : currentPos - 1; + var t = _token(); + tokenMax = (this.char < 0) ? currentPos - 1 : currentPos - 2; + return t; + } + + function _token() { + #else + if( !tokens.isEmpty() ) + return tokens.pop(); + #end + var char; + if( this.char < 0 ) + char = readChar(); + else { + char = this.char; + this.char = -1; + } + while( true ) { + if( StringTools.isEof(char) ) { + this.char = char; + return TEof; + } + switch( char ) { + case 0: + return TEof; + case 32,9,13: // space, tab, CR + #if hscriptPos + tokenMin++; + #end + case 10: line++; // LF + #if hscriptPos + tokenMin++; + #end + case 48,49,50,51,52,53,54,55,56,57: // 0...9 + var n = (char - 48) * 1.0; + var exp = 0.; + while( true ) { + char = readChar(); + exp *= 10; + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + n = n * 10 + (char - 48); + case "e".code, "E".code: + var tk = token(); + var pow : Null = null; + switch( tk ) { + case TConst(CInt(e)): pow = e; + case TOp("-"): + tk = token(); + switch( tk ) { + case TConst(CInt(e)): pow = -e; + default: push(tk); + } + default: + push(tk); + } + if( pow == null ) + invalidChar(char); + if( exp == 0 ) + exp = 10; + return TConst(CFloat((Math.pow(10, pow) / exp) * n * 10)); + case ".".code: + if( exp > 0 ) { + // in case of '0...' + if( exp == 10 && readChar() == ".".code ) { + push(TOp("...")); + var i = Std.int(n); + return TConst( (i == n) ? CInt(i) : CFloat(n) ); + } + invalidChar(char); + } + exp = 1.; + case "x".code: + if( n > 0 || exp > 0 ) + invalidChar(char); + // read hexa + var n = 0; + while( true ) { + char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: // 0-9 + n = (n << 4) + char - 48; + case 65,66,67,68,69,70: // A-F + n = (n << 4) + (char - 55); + case 97,98,99,100,101,102: // a-f + n = (n << 4) + (char - 87); + default: + this.char = char; + return TConst(CInt(n)); + } + } + default: + this.char = char; + var i = Std.int(n); + return TConst( (exp > 0) ? CFloat(n * 10 / exp) : ((i == n) ? CInt(i) : CFloat(n)) ); + } + } + case ";".code: return TSemicolon; + case "(".code: return TPOpen; + case ")".code: return TPClose; + case ",".code: return TComma; + case ".".code: + char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + var n = char - 48; + var exp = 1; + while( true ) { + char = readChar(); + exp *= 10; + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + n = n * 10 + (char - 48); + default: + this.char = char; + return TConst( CFloat(n/exp) ); + } + } + case ".".code: + char = readChar(); + if( char != ".".code ) + invalidChar(char); + return TOp("..."); + default: + this.char = char; + return TDot; + } + case "{".code: return TBrOpen; + case "}".code: return TBrClose; + case "[".code: return TBkOpen; + case "]".code: return TBkClose; + case "'".code, '"'.code: return TConst( CString(readString(char)) ); + case "?".code: + char = readChar(); + if( char == ".".code ) + return TQuestionDot; + this.char = char; + return TQuestion; + case ":".code: return TDoubleDot; + case '='.code: + char = readChar(); + if( char == '='.code ) + return TOp("=="); + else if ( char == '>'.code ) + return TOp("=>"); + this.char = char; + return TOp("="); + case '@'.code: + char = readChar(); + if( idents[char] || char == ':'.code ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( !idents[char] ) { + this.char = char; + return TMeta(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + case '#'.code: + char = readChar(); + if( idents[char] ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( !idents[char] ) { + this.char = char; + return preprocess(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + default: + if( ops[char] ) { + var op = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( StringTools.isEof(char) ) char = 0; + if( !ops[char] ) { + this.char = char; + return TOp(op); + } + var pop = op; + op += String.fromCharCode(char); + if( !opPriority.exists(op) && opPriority.exists(pop) ) { + if( op == "//" || op == "/*" ) + return tokenComment(op,char); + this.char = char; + return TOp(pop); + } + } + } + if( idents[char] ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( StringTools.isEof(char) ) char = 0; + if( !idents[char] ) { + this.char = char; + return TId(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + } + char = readChar(); + } + return null; + } + + function preprocValue( id : String ) : Dynamic { + return preprocesorValues.get(id); + } + + var preprocStack : Array<{ r : Bool }>; + + function parsePreproCond() { + var tk = token(); + return switch( tk ) { + case TPOpen: + push(TPOpen); + parseExpr(); + case TId(id): + mk(EIdent(id), tokenMin, tokenMax); + case TOp("!"): + mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax); + default: + unexpected(tk); + } + } + + function evalPreproCond( e : Expr ) { + switch( expr(e) ) { + case EIdent(id): + return preprocValue(id) != null; + case EUnop("!", _, e): + return !evalPreproCond(e); + case EParent(e): + return evalPreproCond(e); + case EBinop("&&", e1, e2): + return evalPreproCond(e1) && evalPreproCond(e2); + case EBinop("||", e1, e2): + return evalPreproCond(e1) || evalPreproCond(e2); + default: + error(EInvalidPreprocessor("Can't eval " + expr(e).getName()), currentPos, currentPos); + return false; + } + } + + function preprocess( id : String ) : Token { + switch( id ) { + case "if": + var e = parsePreproCond(); + if( evalPreproCond(e) ) { + preprocStack.push({ r : true }); + return token(); + } + preprocStack.push({ r : false }); + skipTokens(); + return token(); + case "else", "elseif" if( preprocStack.length > 0 ): + if( preprocStack[preprocStack.length - 1].r ) { + preprocStack[preprocStack.length - 1].r = false; + skipTokens(); + return token(); + } else if( id == "else" ) { + preprocStack.pop(); + preprocStack.push({ r : true }); + return token(); + } else { + // elseif + preprocStack.pop(); + return preprocess("if"); + } + case "end" if( preprocStack.length > 0 ): + preprocStack.pop(); + return token(); + default: + return TPrepro(id); + } + } + + function skipTokens() { + var spos = preprocStack.length - 1; + var obj = preprocStack[spos]; + var pos = currentPos; + while( true ) { + var tk = token(); + if( tk == TEof ) + error(EInvalidPreprocessor("Unclosed"), pos, pos); + if( preprocStack[spos] != obj ) { + push(tk); + break; + } + } + } + + function tokenComment( op : String, char : Int ) { + var c = op.charCodeAt(1); + var s = input; + if( c == '/'.code ) { // comment + while( char != '\r'.code && char != '\n'.code ) { + char = readChar(); + if( StringTools.isEof(char) ) break; + } + this.char = char; + return token(); + } + if( c == '*'.code ) { /* comment */ + var old = line; + if( op == "/**/" ) { + this.char = char; + return token(); + } + while( true ) { + while( char != '*'.code ) { + if( char == '\n'.code ) line++; + char = readChar(); + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedComment, tokenMin, tokenMin); + break; + } + } + char = readChar(); + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedComment, tokenMin, tokenMin); + break; + } + if( char == '/'.code ) + break; + } + return token(); + } + this.char = char; + return TOp(op); + } + + function constString( c ) { + return switch(c) { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): s; // TODO : escape + quote + } + } + + function tokenString( t ) { + return switch( t ) { + case TEof: ""; + case TConst(c): constString(c); + case TId(s): s; + case TOp(s): s; + case TPOpen: "("; + case TPClose: ")"; + case TBrOpen: "{"; + case TBrClose: "}"; + case TDot: "."; + case TQuestionDot: "?."; + case TComma: ","; + case TSemicolon: ";"; + case TBkOpen: "["; + case TBkClose: "]"; + case TQuestion: "?"; + case TDoubleDot: ":"; + case TMeta(id): "@" + id; + case TPrepro(id): "#" + id; + } + } + +} diff --git a/lib/hscript/hscript/Printer.hx b/lib/hscript/hscript/Printer.hx new file mode 100644 index 00000000..db1ebda0 --- /dev/null +++ b/lib/hscript/hscript/Printer.hx @@ -0,0 +1,374 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr; + +class Printer { + + var buf : StringBuf; + var tabs : String; + + public function new() { + } + + public function exprToString( e : Expr ) { + buf = new StringBuf(); + tabs = ""; + expr(e); + return buf.toString(); + } + + public function typeToString( t : CType ) { + buf = new StringBuf(); + tabs = ""; + type(t); + return buf.toString(); + } + + inline function add(s:T) buf.add(s); + + function type( t : CType ) { + switch( t ) { + case CTOpt(t): + add('?'); + type(t); + case CTPath(path, params): + add(path.join(".")); + if( params != null ) { + add("<"); + var first = true; + for( p in params ) { + if( first ) first = false else add(", "); + type(p); + } + add(">"); + } + case CTNamed(name, t): + add(name); + add(':'); + type(t); + case CTFun(args, ret) if (Lambda.exists(args, function (a) return a.match(CTNamed(_, _)))): + add('('); + for (a in args) + switch a { + case CTNamed(_, _): type(a); + default: type(CTNamed('_', a)); + } + add(')->'); + type(ret); + case CTFun(args, ret): + if( args.length == 0 ) + add("Void -> "); + else { + for( a in args ) { + type(a); + add(" -> "); + } + } + type(ret); + case CTAnon(fields): + add("{"); + var first = true; + for( f in fields ) { + if( first ) { first = false; add(" "); } else add(", "); + add(f.name + " : "); + type(f.t); + } + add(first ? "}" : " }"); + case CTParent(t): + add("("); + type(t); + add(")"); + case CTExpr(e): + expr(e); + } + } + + function addType( t : CType ) { + if( t != null ) { + add(" : "); + type(t); + } + } + + function addConst( c : Const ) { + switch( c ) { + case CInt(i): add(i); + case CFloat(f): add(f); + case CString(s): add('"'); add(s.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r").split("\t").join("\\t")); add('"'); + } + } + + function expr( e : Expr ) { + if( e == null ) { + add("??NULL??"); + return; + } + switch( #if hscriptPos e.e #else e #end ) { + case EConst(c): addConst(c); + case EIdent(v): + add(v); + case EVar(n, t, e): + add("var " + n); + addType(t); + if( e != null ) { + add(" = "); + expr(e); + } + case EParent(e): + add("("); expr(e); add(")"); + case EBlock(el): + if( el.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( e in el ) { + add(tabs); + expr(e); + add(";\n"); + } + tabs = tabs.substr(1); + add("}"); + } + case EField(e, f): + expr(e); + add("." + f); + case EBinop(op, e1, e2): + expr(e1); + add(" " + op + " "); + expr(e2); + case EUnop(op, pre, e): + if( pre ) { + add(op); + expr(e); + } else { + expr(e); + add(op); + } + case ECall(e, args): + if( e == null ) + expr(e); + else switch( #if hscriptPos e.e #else e #end ) { + case EField(_), EIdent(_), EConst(_): + expr(e); + default: + add("("); + expr(e); + add(")"); + } + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(a); + } + add(")"); + case EIf(cond,e1,e2): + add("if( "); + expr(cond); + add(" ) "); + expr(e1); + if( e2 != null ) { + add(" else "); + expr(e2); + } + case EWhile(cond,e): + add("while( "); + expr(cond); + add(" ) "); + expr(e); + case EDoWhile(cond,e): + add("do "); + expr(e); + add(" while ( "); + expr(cond); + add(" )"); + case EFor(v, it, e): + add("for( "+v+" in "); + expr(it); + add(" ) "); + expr(e); + case EForGen(it, e): + add("for( "); + expr(it); + add(" ) "); + expr(e); + case EBreak: + add("break"); + case EContinue: + add("continue"); + case EFunction(params, e, name, ret): + add("function"); + if( name != null ) + add(" " + name); + add("("); + var first = true; + for( a in params ) { + if( first ) first = false else add(", "); + if( a.opt ) add("?"); + add(a.name); + addType(a.t); + } + add(")"); + addType(ret); + add(" "); + expr(e); + case EReturn(e): + add("return"); + if( e != null ) { + add(" "); + expr(e); + } + case EArray(e,index): + expr(e); + add("["); + expr(index); + add("]"); + case EArrayDecl(el): + add("["); + var first = true; + for( e in el ) { + if( first ) first = false else add(", "); + expr(e); + } + add("]"); + case ENew(cl, args): + add("new " + cl + "("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + case EThrow(e): + add("throw "); + expr(e); + case ETry(e, v, t, ecatch): + add("try "); + expr(e); + add(" catch( " + v); + addType(t); + add(") "); + expr(ecatch); + case EObject(fl): + if( fl.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( f in fl ) { + add(tabs); + add(f.name+" : "); + expr(f.e); + add(",\n"); + } + tabs = tabs.substr(1); + add("}"); + } + case ETernary(c,e1,e2): + expr(c); + add(" ? "); + expr(e1); + add(" : "); + expr(e2); + case ESwitch(e, cases, def): + add("switch( "); + expr(e); + add(") {"); + for( c in cases ) { + add("case "); + var first = true; + for( v in c.values ) { + if( first ) first = false else add(", "); + expr(v); + } + add(": "); + expr(c.expr); + add(";\n"); + } + if( def != null ) { + add("default: "); + expr(def); + add(";\n"); + } + add("}"); + case EMeta(name, args, e): + add("@"); + add(name); + if( args != null && args.length > 0 ) { + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + } + add(" "); + expr(e); + case ECheckType(e, t): + add("("); + expr(e); + add(" : "); + addType(t); + add(")"); + case ECast(e,t): + if( t == null ) { + add("cast "); + expr(e); + } else { + add("cast("); + expr(e); + add(","); + addType(t); + add(")"); + } + } + } + + public static function toString( e : Expr ) { + return new Printer().exprToString(e); + } + + public static function errorToString( e : Expr.Error ) { + var message = switch( #if hscriptPos e.e #else e #end ) { + case EInvalidChar(c): "Invalid character: '"+(StringTools.isEof(c) ? "EOF" : String.fromCharCode(c))+"' ("+c+")"; + case EUnexpected(s): "Unexpected token: \""+s+"\""; + case EUnterminatedString: "Unterminated string"; + case EUnterminatedComment: "Unterminated comment"; + case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")"; + case EUnknownVariable(v): "Unknown variable: "+v; + case EInvalidIterator(v): "Invalid iterator: "+v; + case EInvalidOp(op): "Invalid operator: "+op; + case EInvalidAccess(f): "Invalid access to field " + f; + case ECustom(msg): msg; + }; + #if hscriptPos + return e.origin + ":" + e.line + ": " + message; + #else + return message; + #end + } + + +} diff --git a/lib/hscript/hscript/Tools.hx b/lib/hscript/hscript/Tools.hx new file mode 100644 index 00000000..3f10018c --- /dev/null +++ b/lib/hscript/hscript/Tools.hx @@ -0,0 +1,133 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 hscript; +import hscript.Expr; + +class Tools { + + public static function iter( e : Expr, f : Expr -> Void ) { + switch( expr(e) ) { + case EConst(_), EIdent(_): + case EVar(_, _, e): if( e != null ) f(e); + case EParent(e): f(e); + case EBlock(el): for( e in el ) f(e); + case EField(e, _): f(e); + case EBinop(_, e1, e2): f(e1); f(e2); + case EUnop(_, _, e): f(e); + case ECall(e, args): f(e); for( a in args ) f(a); + case EIf(c, e1, e2): f(c); f(e1); if( e2 != null ) f(e2); + case EWhile(c, e): f(c); f(e); + case EDoWhile(c, e): f(c); f(e); + case EFor(_, it, e): f(it); f(e); + case EForGen(it, e): f(it); f(e); + case EBreak,EContinue: + case EFunction(_, e, _, _): f(e); + case EReturn(e): if( e != null ) f(e); + case EArray(e, i): f(e); f(i); + case EArrayDecl(el): for( e in el ) f(e); + case ENew(_,el): for( e in el ) f(e); + case EThrow(e): f(e); + case ETry(e, _, _, c): f(e); f(c); + case EObject(fl): for( fi in fl ) f(fi.e); + case ETernary(c, e1, e2): f(c); f(e1); f(e2); + case ESwitch(e, cases, def): + f(e); + for( c in cases ) { + for( v in c.values ) f(v); + f(c.expr); + } + if( def != null ) f(def); + case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e); + case ECheckType(e,_): f(e); + case ECast(e,_): f(e); + } + } + + public static function map( e : Expr, f : Expr -> Expr ) { + var edef = switch( expr(e) ) { + case EConst(_), EIdent(_), EBreak, EContinue: expr(e); + case EVar(n, t, e): EVar(n, t, if( e != null ) f(e) else null); + case EParent(e): EParent(f(e)); + case EBlock(el): EBlock([for( e in el ) f(e)]); + case EField(e, fi): EField(f(e),fi); + case EBinop(op, e1, e2): EBinop(op, f(e1), f(e2)); + case EUnop(op, pre, e): EUnop(op, pre, f(e)); + case ECall(e, args): ECall(f(e),[for( a in args ) f(a)]); + case EIf(c, e1, e2): EIf(f(c),f(e1),if( e2 != null ) f(e2) else null); + case EWhile(c, e): EWhile(f(c),f(e)); + case EDoWhile(c, e): EDoWhile(f(c),f(e)); + case EFor(v, it, e): EFor(v, f(it), f(e)); + case EForGen(it, e): EForGen(f(it), f(e)); + case EFunction(args, e, name, t): EFunction(args, f(e), name, t); + case EReturn(e): EReturn(if( e != null ) f(e) else null); + case EArray(e, i): EArray(f(e),f(i)); + case EArrayDecl(el): EArrayDecl([for( e in el ) f(e)]); + case ENew(cl,el): ENew(cl,[for( e in el ) f(e)]); + case EThrow(e): EThrow(f(e)); + case ETry(e, v, t, c): ETry(f(e), v, t, f(c)); + case EObject(fl): EObject([for( fi in fl ) { name : fi.name, e : f(fi.e) }]); + case ETernary(c, e1, e2): ETernary(f(c), f(e1), f(e2)); + case ESwitch(e, cases, def): ESwitch(f(e), [for( c in cases ) { values : [for( v in c.values ) f(v)], expr : f(c.expr) } ], def == null ? null : f(def)); + case EMeta(name, args, e): EMeta(name, args == null ? null : [for( a in args ) f(a)], f(e)); + case ECheckType(e,t): ECheckType(f(e), t); + case ECast(e,t): ECast(f(e),t); + } + return mk(edef, e); + } + + public static inline function expr( e : Expr ) : ExprDef { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + public static inline function mk( e : ExprDef, p : Expr ) { + #if hscriptPos + return { e : e, pmin : p.pmin, pmax : p.pmax, origin : p.origin, line : p.line }; + #else + return e; + #end + } + + public static inline function getKeyIterator( e : Expr, callb : String -> String -> Expr -> T ) { + var key = null, value = null, it = e; + switch( expr(it) ) { + case EBinop("in", ekv, eiter): + switch( expr(ekv) ) { + case EBinop("=>",v1,v2): + switch( [expr(v1),expr(v2)] ) { + case [EIdent(v1), EIdent(v2)]: + key = v1; + value = v2; + it = eiter; + default: + } + default: + } + default: + } + return callb(key,value,it); + } + +} \ No newline at end of file diff --git a/lib/hscript/release.bat b/lib/hscript/release.bat new file mode 100644 index 00000000..8d4202cc --- /dev/null +++ b/lib/hscript/release.bat @@ -0,0 +1,20 @@ +@echo off +set PATH="C:\Program Files\7-Zip";%PATH% +rm -rf release +mkdir release +cp haxelib.json README.md extraParams.hxml release +cd release +mkdir hscript +mkdir script +cd .. +cp hscript/*.hx release/hscript +cp script/*.hx* release/script +cd release/script +haxe build.hxml +cd ../.. +haxe -xml release/haxedoc.xml hscript.Interp hscript.Parser hscript.Bytes hscript.Macro +7z a -tzip release.zip release +rm -rf release +haxelib submit release.zip +echo Remember to "git tag vX.Y.Z && git push --tags" +pause \ No newline at end of file diff --git a/lib/hscript/script/RunScript.hx b/lib/hscript/script/RunScript.hx new file mode 100644 index 00000000..d41eb75f --- /dev/null +++ b/lib/hscript/script/RunScript.hx @@ -0,0 +1,154 @@ +package; + +import haxe.*; +import hscript.*; +import sys.*; +import sys.io.*; +import sys.net.*; + +#if neko +import neko.Lib; +#end +#if cpp +import cpp.Lib; +#end + +class RunScript { + + public static function main () { + + var args = Sys.args (); + var workingDirectory = args.pop(); + + try { + Sys.setCwd(workingDirectory); + } catch (e:Dynamic) { + error("Failed to set current working directory to [" + workingDirectory + "]"); + } + + if (args.length == 0) { + error("Argument missing. Expected 1 got 0"); + } + + if (args[0] == "-f" || args[0] == "--file") { + if (args.length == 1) { + error("Argument missing. Expected 2 got 1"); + } + if (args.length > 2) { + error("Too many arguments. Expected 2 got " + args.length); + } + executeScriptFile(args[1]); + } + + if (args[0] == "--help") { + printHelp(); + } + + executeScript(args.join(" ")); + } + + static function executeScriptFile(scriptPath:String) { + if (!FileSystem.exists(scriptPath)) { + error("Specified file [" + scriptPath + "] not found"); + } + if (FileSystem.isDirectory(scriptPath)) { + error("Specified file [" + scriptPath + "] is a directory"); + } + var script = File.getContent(scriptPath); + executeScript(script); + } + + static function executeScript(script:String) { + var parser = new hscript.Parser(); + var program = parser.parseString(script); + var interp = new hscript.Interp(); + + // export some useful classes + interp.variables.set("Array", Array); + interp.variables.set("DateTools", DateTools); + interp.variables.set("Math", Math); + interp.variables.set("StringTools", StringTools); + interp.variables.set("Sys", Sys); + interp.variables.set("Xml", Xml); + interp.variables.set("sys", { + "FileSystem": sys.FileSystem, + "io": { + "File": sys.io.File + }, + "net": { + "Host": sys.net.Host + } + }); + interp.variables.set("haxe", { + "Json": haxe.Json, + "Http": haxe.Http, + "Serializer": haxe.Serializer, + "Unserializer": haxe.Unserializer + }); + + info(interp.execute(program)); + } + + static function printHelp(exit:Bool = true) { + var hscriptPath = getHaxelibPath("hscript"); + var meta:Dynamic = Json.parse(File.getContent(hscriptPath + "haxelib.json")); + info('${meta.name} v${meta.version}'); + info('${meta.description}'); + info(""); + info("usage: haxelib run hscript SCRIPT"); + info(" or: haxelib run hscript -f SCRIPTPATH"); + info(" or: haxelib run hscript --file SCRIPTPATH"); + info(""); + info("examples:"); + info(" haxelib run hscript var x = 4; 1 + 2 * x"); + info(" 9"); + info(""); + info(" haxelib run hscript \"5 | 6\""); + info(" 7"); + info(""); + info(" haxelib run hscript for(i in 0...5) Sys.stdout().writeString(i + ', '); 'done'"); + info(" 0, 1, 2, 3, 4, done"); + info(""); + info(" haxelib run hscript sys.net.Host.localhost()"); + info(" mycomputer"); + info(""); + if(Sys.systemName() == "Windows") { + info(" for /f %i in ('haxelib run hscript \"new sys.net.Host('')\"') do @set MY_IP=%i"); + info(" echo %MY_IP%"); + } else { + info(" MY_IP=$(haxelib run hscript \"new sys.net.Host('')\"')"); + info(" echo $MY_IP"); + } + info(" 192.168.248.1"); + if(exit) + Sys.exit(0); + } + + static function info(msg:Dynamic) { + Sys.stdout().writeString("" + msg + "\n"); + } + + static function error(msg:Dynamic, exit:Bool = true) { + Sys.stderr().writeString("Error: " + msg + "!\n"); + if(exit) + Sys.exit(1); + } + + static function getHaxelibPath(libraryName:String):String { + var proc = new Process("haxelib", ["path", libraryName]); + var result = ""; + var ex:Dynamic = null; + try { + while(true) { + var line = proc.stdout.readLine(); + if (line.substr(0, 1) != "-") + result = line; + } + } catch (e:Dynamic) { + ex = e; + }; + proc.close(); + if (ex) error(ex); + return result; + } +} diff --git a/lib/hscript/script/build.hxml b/lib/hscript/script/build.hxml new file mode 100644 index 00000000..0b244ab3 --- /dev/null +++ b/lib/hscript/script/build.hxml @@ -0,0 +1,4 @@ +-main RunScript +-neko ../run.n +-lib hscript +-dce no -- 2.40.1