From 67a953e41dc8d79f41eff25ae04670e0f82bc403 Mon Sep 17 00:00:00 2001
From: liteng <930372551@qq.com>
Date: Fri, 16 Nov 2018 20:38:26 +0800
Subject: [PATCH] =?UTF-8?q?ShadowEditor=E6=A0=B8=E5=BF=83=E7=B1=BB?=
=?UTF-8?q?=E5=BA=93=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
ShadowEditor.Core/README.md | 7 +
ShadowEditor.Core/index.html | 133 ++
ShadowEditor.Core/rollup.config.js | 42 +
ShadowEditor.Core/src/Application.js | 151 ++
ShadowEditor.Core/src/Options.js | 26 +
ShadowEditor.Core/src/animation/Animation.js | 48 +
.../src/animation/AnimationGroup.js | 57 +
.../src/animation/AnimationManager.js | 98 ++
.../src/animation/AnimationType.js | 22 +
ShadowEditor.Core/src/animation/Ease.js | 456 ++++++
ShadowEditor.Core/src/api/API.js | 12 +
ShadowEditor.Core/src/api/UIAPI.js | 12 +
.../src/command/AddObjectCommand.js | 56 +
.../src/command/AddScriptCommand.js | 64 +
ShadowEditor.Core/src/command/Command.js | 36 +
.../src/command/MoveObjectCommand.js | 94 ++
.../src/command/MultiCmdsCommand.js | 62 +
.../src/command/RemoveObjectCommand.js | 87 +
.../src/command/RemoveScriptCommand.js | 68 +
.../src/command/SetColorCommand.js | 65 +
.../src/command/SetGeometryCommand.js | 75 +
.../src/command/SetGeometryValueCommand.js | 64 +
.../src/command/SetMaterialColorCommand.js | 65 +
.../src/command/SetMaterialCommand.js | 64 +
.../src/command/SetMaterialMapCommand.js | 113 ++
.../src/command/SetMaterialValueCommand.js | 69 +
.../src/command/SetPositionCommand.js | 71 +
.../src/command/SetRotationCommand.js | 71 +
.../src/command/SetScaleCommand.js | 71 +
.../src/command/SetSceneCommand.js | 78 +
.../src/command/SetScriptValueCommand.js | 84 +
.../src/command/SetUuidCommand.js | 62 +
.../src/command/SetValueCommand.js | 65 +
.../src/component/BaseComponent.js | 20 +
.../src/component/BasicComponent.js | 126 ++
.../src/component/CameraComponent.js | 124 ++
.../src/component/FireComponent.js | 222 +++
.../src/component/GeometryComponent.js | 99 ++
.../src/component/LMeshComponent.js | 158 ++
.../src/component/LightComponent.js | 342 ++++
.../src/component/MMDComponent.js | 114 ++
.../src/component/MaterialComponent.js | 1443 +++++++++++++++++
.../src/component/ParticleEmitterComponent.js | 583 +++++++
.../src/component/ReflectorComponent.js | 258 +++
.../src/component/SceneComponent.js | 613 +++++++
.../src/component/ShadowComponent.js | 363 +++++
.../src/component/SmokeComponent.js | 118 ++
.../src/component/TransformComponent.js | 238 +++
.../animation/BasicAnimationComponent.js | 194 +++
.../animation/TweenAnimationComponent.js | 556 +++++++
.../component/audio/AudioListenerComponent.js | 92 ++
.../audio/BackgroundMusicComponent.js | 216 +++
.../geometry/BoxGeometryComponent.js | 169 ++
.../geometry/CircleGeometryComponent.js | 132 ++
.../geometry/CylinderGeometryComponent.js | 165 ++
.../geometry/IcosahedronGeometryComponent.js | 100 ++
.../geometry/LatheGeometryComponent.js | 118 ++
.../geometry/PlaneGeometryComponent.js | 136 ++
.../geometry/SphereGeometryComponent.js | 181 +++
.../geometry/TeapotGeometryComponent.js | 197 +++
.../geometry/TorusGeometryComponent.js | 149 ++
.../geometry/TorusKnotGeometryComponent.js | 165 ++
.../object/PerlinTerrainComponent.js | 175 ++
.../object/ShaderTerrainComponent.js | 0
.../src/component/object/SkyComponent.js | 174 ++
.../physics/PhysicsWorldComponent.js | 140 ++
.../component/physics/RigidBodyComponent.js | 159 ++
.../shader/raw_shader_material_fragment.glsl | 5 +
.../shader/raw_shader_material_vertex.glsl | 10 +
.../shader/shader_material_fragment.glsl | 3 +
.../shader/shader_material_vertex.glsl | 3 +
.../src/component/water/WaterComponent.js | 175 ++
ShadowEditor.Core/src/core/History.js | 213 +++
.../src/core/ServerModelUserData.js | 21 +
ShadowEditor.Core/src/core/Storage.js | 75 +
ShadowEditor.Core/src/core/UserData.js | 9 +
.../src/core/userData/AudioUserDataItem.js | 14 +
.../src/core/userData/UserDataItemBase.js | 8 +
ShadowEditor.Core/src/editor/Editor.js | 396 +++++
ShadowEditor.Core/src/editor/Physics.js | 171 ++
ShadowEditor.Core/src/editor/StatusBar.js | 100 ++
ShadowEditor.Core/src/editor/Toolbar.js | 134 ++
ShadowEditor.Core/src/editor/Viewport.js | 26 +
.../src/editor/bottom/AnimationPanel.js | 432 +++++
.../src/editor/bottom/AudioPanel.js | 30 +
.../src/editor/bottom/BottomPanel.js | 197 +++
.../src/editor/bottom/LogPanel.js | 85 +
.../src/editor/bottom/ModelPanel.js | 30 +
.../src/editor/bottom/ParticlePanel.js | 30 +
.../src/editor/bottom/TexturePanel.js | 30 +
.../editor/control/TextureSelectControl.js | 119 ++
.../src/editor/menubar/AssetMenu.js | 282 ++++
.../src/editor/menubar/ComponentMenu.js | 205 +++
.../src/editor/menubar/EditMenu.js | 192 +++
.../src/editor/menubar/GeometryMenu.js | 198 +++
.../src/editor/menubar/HelpMenu.js | 71 +
.../src/editor/menubar/LightMenu.js | 177 ++
ShadowEditor.Core/src/editor/menubar/Logo.js | 29 +
.../src/editor/menubar/Menubar.js | 62 +
.../src/editor/menubar/OptionsMenu.js | 84 +
.../src/editor/menubar/PhysicsMenu.js | 68 +
.../src/editor/menubar/PlayMenu.js | 56 +
.../src/editor/menubar/SceneMenu.js | 245 +++
.../src/editor/menubar/StatusMenu.js | 36 +
.../src/editor/menubar/TerrainMenu.js | 124 ++
.../src/editor/script/ScriptEditor.js | 369 +++++
.../script/code/FragmentShaderStarter.js | 9 +
.../editor/script/code/JavaScriptStarter.js | 68 +
.../src/editor/script/code/JsonStarter.js | 15 +
.../editor/script/code/VertexShaderStarter.js | 14 +
.../src/editor/script/window/ScriptWindow.js | 153 ++
.../src/editor/sidebar/AnimationPanel.js | 30 +
.../src/editor/sidebar/HistoryPanel.js | 91 ++
.../src/editor/sidebar/PropertyPanel.js | 68 +
.../src/editor/sidebar/SettingPanel.js | 208 +++
.../src/editor/sidebar/Sidebar.js | 159 ++
.../src/editor/sidebar2/HierachyPanel.js | 161 ++
.../src/editor/sidebar2/ScriptPanel.js | 151 ++
.../src/editor/sidebar2/Sidebar.js | 56 +
.../src/editor/window/AudioEditWindow.js | 110 ++
.../src/editor/window/AudioWindow.js | 80 +
.../src/editor/window/MMDEditWindow.js | 125 ++
.../src/editor/window/MMDWindow.js | 118 ++
.../src/editor/window/ModelEditWindow.js | 125 ++
.../src/editor/window/ModelWindow.js | 105 ++
.../src/editor/window/OptionsWindow.js | 401 +++++
.../src/editor/window/SceneEditWindow.js | 125 ++
.../src/editor/window/SceneWindow.js | 205 +++
.../src/editor/window/TextureEditWindow.js | 110 ++
.../src/editor/window/TextureSettingWindow.js | 461 ++++++
.../src/editor/window/TextureWindow.js | 82 +
ShadowEditor.Core/src/effect/BaseEffect.js | 17 +
ShadowEditor.Core/src/effect/SelectEffect.js | 50 +
.../src/effect/shader/select_fragment.glsl | 5 +
.../src/effect/shader/select_vertex.glsl | 12 +
ShadowEditor.Core/src/event/AnimateEvent.js | 41 +
ShadowEditor.Core/src/event/BaseEvent.js | 26 +
.../src/event/EditorControlsEvent.js | 49 +
.../src/event/EventDispatcher.js | 135 ++
ShadowEditor.Core/src/event/EventList.js | 93 ++
ShadowEditor.Core/src/event/KeyDownEvent.js | 68 +
ShadowEditor.Core/src/event/ObjectEvent.js | 81 +
ShadowEditor.Core/src/event/PickEvent.js | 109 ++
ShadowEditor.Core/src/event/RenderEvent.js | 101 ++
ShadowEditor.Core/src/event/ResizeEvent.js | 40 +
.../src/event/TransformControlsEvent.js | 174 ++
ShadowEditor.Core/src/event/menu/MenuEvent.js | 23 +
.../menu/physics/AddPhysicsClothEvent.js | 145 ++
.../menu/physics/AddPhysicsPlaneEvent.js | 45 +
.../event/menu/physics/AddPhysicsWallEvent.js | 72 +
ShadowEditor.Core/src/index.js | 6 +
ShadowEditor.Core/src/loader/AMFLoader.js | 25 +
ShadowEditor.Core/src/loader/AWDLoader.js | 26 +
ShadowEditor.Core/src/loader/BabylonLoader.js | 28 +
ShadowEditor.Core/src/loader/BaseLoader.js | 17 +
ShadowEditor.Core/src/loader/BinaryLoader.js | 27 +
ShadowEditor.Core/src/loader/CTMLoader.js | 28 +
ShadowEditor.Core/src/loader/ColladaLoader.js | 27 +
ShadowEditor.Core/src/loader/FBXLoader.js | 26 +
ShadowEditor.Core/src/loader/GLTFLoader.js | 27 +
ShadowEditor.Core/src/loader/KMZLoader.js | 27 +
ShadowEditor.Core/src/loader/LOLLoader.js | 94 ++
ShadowEditor.Core/src/loader/MD2Loader.js | 34 +
ShadowEditor.Core/src/loader/ModelLoader.js | 77 +
ShadowEditor.Core/src/loader/OBJLoader.js | 26 +
ShadowEditor.Core/src/loader/ObjectLoader.js | 71 +
ShadowEditor.Core/src/loader/PLYLoader.js | 28 +
ShadowEditor.Core/src/loader/PMDLoader.js | 46 +
ShadowEditor.Core/src/loader/STLLoader.js | 28 +
ShadowEditor.Core/src/loader/VTKLoader.js | 28 +
ShadowEditor.Core/src/lol/Animation.js | 28 +
ShadowEditor.Core/src/lol/AnimationBone.js | 137 ++
ShadowEditor.Core/src/lol/BaseAnimations.js | 24 +
ShadowEditor.Core/src/lol/Bone.js | 28 +
ShadowEditor.Core/src/lol/Constant.js | 7 +
ShadowEditor.Core/src/lol/DataView2.js | 107 ++
ShadowEditor.Core/src/lol/HiddenBones.js | 921 +++++++++++
ShadowEditor.Core/src/lol/Model.js | 417 +++++
ShadowEditor.Core/src/lol/Texture.js | 30 +
ShadowEditor.Core/src/lol/Vertex.js | 22 +
.../src/object/component/Fire.js | 42 +
.../src/object/component/ParticleEmitter.js | 62 +
ShadowEditor.Core/src/object/component/Sky.js | 60 +
.../src/object/component/Smoke.js | 78 +
.../component/shader/smoke_fragment.glsl | 9 +
.../object/component/shader/smoke_vertex.glsl | 23 +
ShadowEditor.Core/src/object/geometry/Box.js | 17 +
.../src/object/geometry/Circle.js | 17 +
.../src/object/geometry/Cylinder.js | 17 +
.../src/object/geometry/Group.js | 12 +
.../src/object/geometry/Icosahedron.js | 17 +
.../src/object/geometry/Lathe.js | 31 +
.../src/object/geometry/Plane.js | 18 +
.../src/object/geometry/Sphere.js | 17 +
.../src/object/geometry/Sprite.js | 14 +
.../src/object/geometry/Teapot.js | 31 +
ShadowEditor.Core/src/object/geometry/Text.js | 43 +
.../src/object/geometry/Torus.js | 17 +
.../src/object/geometry/TorusKnot.js | 17 +
.../src/object/light/HemisphereLight.js | 38 +
.../src/object/light/PhysicalLight.js | 53 +
.../src/object/light/PointLight.js | 41 +
.../src/object/light/RectAreaLight.js | 31 +
.../light/shader/hemisphere_fragment.glsl | 11 +
.../light/shader/hemisphere_vertex.glsl | 7 +
.../src/object/terrain/PerlinTerrain.js | 111 ++
.../src/object/terrain/PhysicsTerrain.js | 170 ++
.../src/object/terrain/ShaderTerrain.js | 179 ++
.../terrain/shader/height_fragment.glsl | 87 +
.../object/terrain/shader/height_vertex.glsl | 8 +
ShadowEditor.Core/src/object/water/Water.js | 137 ++
.../water/shader/heightmap_fragment.glsl | 30 +
.../object/water/shader/smooth_fragment.glsl | 14 +
.../src/object/water/shader/water_vertex.glsl | 52 +
ShadowEditor.Core/src/physics/PhysicsData.js | 37 +
ShadowEditor.Core/src/physics/PlysicsUtils.js | 81 +
ShadowEditor.Core/src/player/Player.js | 163 ++
.../src/player/animator/MMDAnimator.js | 35 +
.../src/player/animator/ParticleAnimator.js | 37 +
.../src/player/animator/TweenAnimator.js | 80 +
.../src/player/component/PlayerAnimation.js | 94 ++
.../src/player/component/PlayerAudio.js | 50 +
.../src/player/component/PlayerComponent.js | 43 +
.../src/player/component/PlayerEvent.js | 167 ++
.../src/player/component/PlayerLoader.js | 70 +
.../src/player/component/PlayerPhysics.js | 99 ++
ShadowEditor.Core/src/polyfills.js | 6 +
.../src/serialization/BaseSerializer.js | 40 +
.../src/serialization/Converter.js | 380 +++++
.../src/serialization/Metadata.js | 11 +
.../serialization/app/AnimationSerializer.js | 78 +
.../serialization/app/OptionsSerializer.js | 33 +
.../src/serialization/app/ScriptSerializer.js | 52 +
.../audio/AudioListenerSerializer.js | 33 +
.../serialization/audio/AudioSerializer.js | 40 +
.../serialization/camera/CameraSerializer.js | 45 +
.../serialization/camera/CamerasSerializer.js | 48 +
.../camera/OrthographicCameraSerializer.js | 47 +
.../camera/PerspectiveCameraSerializer.js | 49 +
.../src/serialization/core/BoneSerializer.js | 29 +
.../src/serialization/core/GroupSerializer.js | 27 +
.../src/serialization/core/MeshSerializer.js | 56 +
.../serialization/core/Object3DSerializer.js | 86 +
.../src/serialization/core/SceneSerializer.js | 59 +
.../src/serialization/core/ServerObject.js | 45 +
.../serialization/core/SpriteSerializer.js | 50 +
.../core/WebGLRenderTargetSerializer.js | 49 +
.../core/WebGLRendererSerializer.js | 62 +
.../core/WebGLShadowMapSerializer.js | 42 +
.../geometry/BoxBufferGeometrySerializer.js | 34 +
.../geometry/BufferGeometrySerializer.js | 71 +
.../CircleBufferGeometrySerializer.js | 32 +
.../geometry/ConeBufferGeometrySerializer.js | 35 +
.../CylinderBufferGeometrySerializer.js | 36 +
.../DodecahedronBufferGeometrySerializer.js | 30 +
.../ExtrudeBufferGeometrySerializer.js | 32 +
.../geometry/GeometriesSerializer.js | 86 +
.../geometry/GeometrySerializer.js | 78 +
.../IcosahedronBufferGeometrySerializer.js | 30 +
.../InstancedBufferGeometrySerializer.js | 29 +
.../geometry/LatheBufferGeometrySerializer.js | 32 +
.../OctahedronBufferGeometrySerializer.js | 30 +
.../ParametricBufferGeometrySerializer.js | 31 +
.../geometry/PlaneBufferGeometrySerializer.js | 32 +
.../PolyhedronBufferGeometrySerializer.js | 32 +
.../geometry/RingBufferGeometrySerializer.js | 34 +
.../geometry/ShapeBufferGeometrySerializer.js | 30 +
.../SphereBufferGeometrySerializer.js | 35 +
.../TeapotBufferGeometrySerializer.js | 35 +
.../TetrahedronBufferGeometrySerializer.js | 30 +
.../geometry/TextBufferGeometrySerializer.js | 30 +
.../geometry/TorusBufferGeometrySerializer.js | 33 +
.../TorusKnotBufferGeometrySerializer.js | 34 +
.../geometry/TubeBufferGeometrySerializer.js | 33 +
.../light/AmbientLightSerializer.js | 33 +
.../light/DirectionalLightSerializer.js | 33 +
.../light/HemisphereLightSerializer.js | 35 +
.../serialization/light/LightSerializer.js | 43 +
.../light/PointLightSerializer.js | 35 +
.../light/RectAreaLightSerializer.js | 34 +
.../light/SpotLightSerializer.js | 47 +
.../DirectionalLightShadowSerializer.js | 31 +
.../light/shadow/LightShadowSerializer.js | 45 +
.../light/shadow/LightShadowsSerializer.js | 48 +
.../light/shadow/SpotLightShadowSerializer.js | 31 +
.../material/LineBasicMaterialSerializer.js | 27 +
.../material/LineDashedMaterialSerializer.js | 27 +
.../material/MaterialSerializer.js | 164 ++
.../material/MaterialsSerializer.js | 86 +
.../material/MeshBasicMaterialSerializer.js | 27 +
.../material/MeshDepthMaterialSerializer.js | 27 +
.../MeshDistanceMaterialSerializer.js | 27 +
.../material/MeshFaceMaterialSerializer.js | 27 +
.../material/MeshLambertMaterialSerializer.js | 27 +
.../material/MeshNormalMaterialSerializer.js | 27 +
.../material/MeshPhongMaterialSerializer.js | 27 +
.../MeshPhysicalMaterialSerializer.js | 27 +
.../MeshStandardMaterialSerializer.js | 27 +
.../material/MeshToonMaterialSerializer.js | 27 +
.../material/MultiMaterialSerializer.js | 27 +
.../ParticleBasicMaterialSerializer.js | 27 +
.../ParticleSystemMaterialSerializer.js | 27 +
.../material/PointCloudMaterialSerializer.js | 27 +
.../material/PointsMaterialSerializer.js | 27 +
.../material/RawShaderMaterialSerializer.js | 70 +
.../material/ShaderMaterialSerializer.js | 70 +
.../material/ShadowMaterialSerializer.js | 27 +
.../SpriteCanvasMaterialSerializer.js | 27 +
.../material/SpriteMaterialSerializer.js | 29 +
.../serialization/objects/FireSerializer.js | 39 +
.../objects/ParticleEmitterSerializer.js | 148 ++
.../objects/PerlinTerrainSerializer.js | 36 +
.../objects/ReflectorSerializer.js | 45 +
.../serialization/objects/SkySerializer.js | 28 +
.../serialization/objects/SmokeSerializer.js | 32 +
.../texture/CanvasTextureSerializer.js | 27 +
.../texture/CompressedTextureSerializer.js | 27 +
.../texture/CubeTextureSerializer.js | 61 +
.../texture/DataTextureSerializer.js | 27 +
.../texture/DepthTextureSerializer.js | 27 +
.../texture/TextureSerializer.js | 130 ++
.../texture/TexturesSerializer.js | 56 +
.../texture/VideoTextureSerializer.js | 27 +
ShadowEditor.Core/src/third_party.js | 4 +
ShadowEditor.Core/src/ui/ImageListWindow.js | 227 +++
ShadowEditor.Core/src/ui/ImageUploader.js | 76 +
ShadowEditor.Core/src/ui/Outliner.js | 236 +++
ShadowEditor.Core/src/utils/Ajax.js | 119 ++
ShadowEditor.Core/src/utils/Converter.js | 172 ++
ShadowEditor.Core/src/utils/CssUtils.js | 22 +
ShadowEditor.Core/src/utils/GeometryUtils.js | 61 +
ShadowEditor.Core/src/utils/ImageUtils.js | 22 +
ShadowEditor.Core/src/utils/JsUtils.js | 30 +
ShadowEditor.Core/src/utils/MIMETypeUtils.js | 51 +
ShadowEditor.Core/src/utils/MathUtils.js | 24 +
ShadowEditor.Core/src/utils/Socket.js | 43 +
ShadowEditor.Core/src/utils/StringUtils.js | 41 +
ShadowEditor.Core/src/utils/UploadUtils.js | 40 +
ShadowEditor.Core/src/worker/Worker.js | 0
package.json | 1 +
341 files changed, 30039 insertions(+)
create mode 100644 ShadowEditor.Core/README.md
create mode 100644 ShadowEditor.Core/index.html
create mode 100644 ShadowEditor.Core/rollup.config.js
create mode 100644 ShadowEditor.Core/src/Application.js
create mode 100644 ShadowEditor.Core/src/Options.js
create mode 100644 ShadowEditor.Core/src/animation/Animation.js
create mode 100644 ShadowEditor.Core/src/animation/AnimationGroup.js
create mode 100644 ShadowEditor.Core/src/animation/AnimationManager.js
create mode 100644 ShadowEditor.Core/src/animation/AnimationType.js
create mode 100644 ShadowEditor.Core/src/animation/Ease.js
create mode 100644 ShadowEditor.Core/src/api/API.js
create mode 100644 ShadowEditor.Core/src/api/UIAPI.js
create mode 100644 ShadowEditor.Core/src/command/AddObjectCommand.js
create mode 100644 ShadowEditor.Core/src/command/AddScriptCommand.js
create mode 100644 ShadowEditor.Core/src/command/Command.js
create mode 100644 ShadowEditor.Core/src/command/MoveObjectCommand.js
create mode 100644 ShadowEditor.Core/src/command/MultiCmdsCommand.js
create mode 100644 ShadowEditor.Core/src/command/RemoveObjectCommand.js
create mode 100644 ShadowEditor.Core/src/command/RemoveScriptCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetColorCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetGeometryCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetGeometryValueCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetMaterialColorCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetMaterialCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetMaterialMapCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetMaterialValueCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetPositionCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetRotationCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetScaleCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetSceneCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetScriptValueCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetUuidCommand.js
create mode 100644 ShadowEditor.Core/src/command/SetValueCommand.js
create mode 100644 ShadowEditor.Core/src/component/BaseComponent.js
create mode 100644 ShadowEditor.Core/src/component/BasicComponent.js
create mode 100644 ShadowEditor.Core/src/component/CameraComponent.js
create mode 100644 ShadowEditor.Core/src/component/FireComponent.js
create mode 100644 ShadowEditor.Core/src/component/GeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/LMeshComponent.js
create mode 100644 ShadowEditor.Core/src/component/LightComponent.js
create mode 100644 ShadowEditor.Core/src/component/MMDComponent.js
create mode 100644 ShadowEditor.Core/src/component/MaterialComponent.js
create mode 100644 ShadowEditor.Core/src/component/ParticleEmitterComponent.js
create mode 100644 ShadowEditor.Core/src/component/ReflectorComponent.js
create mode 100644 ShadowEditor.Core/src/component/SceneComponent.js
create mode 100644 ShadowEditor.Core/src/component/ShadowComponent.js
create mode 100644 ShadowEditor.Core/src/component/SmokeComponent.js
create mode 100644 ShadowEditor.Core/src/component/TransformComponent.js
create mode 100644 ShadowEditor.Core/src/component/animation/BasicAnimationComponent.js
create mode 100644 ShadowEditor.Core/src/component/animation/TweenAnimationComponent.js
create mode 100644 ShadowEditor.Core/src/component/audio/AudioListenerComponent.js
create mode 100644 ShadowEditor.Core/src/component/audio/BackgroundMusicComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/BoxGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/CircleGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/CylinderGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/IcosahedronGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/LatheGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/PlaneGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/SphereGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/TeapotGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/TorusGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/geometry/TorusKnotGeometryComponent.js
create mode 100644 ShadowEditor.Core/src/component/object/PerlinTerrainComponent.js
create mode 100644 ShadowEditor.Core/src/component/object/ShaderTerrainComponent.js
create mode 100644 ShadowEditor.Core/src/component/object/SkyComponent.js
create mode 100644 ShadowEditor.Core/src/component/physics/PhysicsWorldComponent.js
create mode 100644 ShadowEditor.Core/src/component/physics/RigidBodyComponent.js
create mode 100644 ShadowEditor.Core/src/component/shader/raw_shader_material_fragment.glsl
create mode 100644 ShadowEditor.Core/src/component/shader/raw_shader_material_vertex.glsl
create mode 100644 ShadowEditor.Core/src/component/shader/shader_material_fragment.glsl
create mode 100644 ShadowEditor.Core/src/component/shader/shader_material_vertex.glsl
create mode 100644 ShadowEditor.Core/src/component/water/WaterComponent.js
create mode 100644 ShadowEditor.Core/src/core/History.js
create mode 100644 ShadowEditor.Core/src/core/ServerModelUserData.js
create mode 100644 ShadowEditor.Core/src/core/Storage.js
create mode 100644 ShadowEditor.Core/src/core/UserData.js
create mode 100644 ShadowEditor.Core/src/core/userData/AudioUserDataItem.js
create mode 100644 ShadowEditor.Core/src/core/userData/UserDataItemBase.js
create mode 100644 ShadowEditor.Core/src/editor/Editor.js
create mode 100644 ShadowEditor.Core/src/editor/Physics.js
create mode 100644 ShadowEditor.Core/src/editor/StatusBar.js
create mode 100644 ShadowEditor.Core/src/editor/Toolbar.js
create mode 100644 ShadowEditor.Core/src/editor/Viewport.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/AnimationPanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/AudioPanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/BottomPanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/LogPanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/ModelPanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/ParticlePanel.js
create mode 100644 ShadowEditor.Core/src/editor/bottom/TexturePanel.js
create mode 100644 ShadowEditor.Core/src/editor/control/TextureSelectControl.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/AssetMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/ComponentMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/EditMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/GeometryMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/HelpMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/LightMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/Logo.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/Menubar.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/OptionsMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/PhysicsMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/PlayMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/SceneMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/StatusMenu.js
create mode 100644 ShadowEditor.Core/src/editor/menubar/TerrainMenu.js
create mode 100644 ShadowEditor.Core/src/editor/script/ScriptEditor.js
create mode 100644 ShadowEditor.Core/src/editor/script/code/FragmentShaderStarter.js
create mode 100644 ShadowEditor.Core/src/editor/script/code/JavaScriptStarter.js
create mode 100644 ShadowEditor.Core/src/editor/script/code/JsonStarter.js
create mode 100644 ShadowEditor.Core/src/editor/script/code/VertexShaderStarter.js
create mode 100644 ShadowEditor.Core/src/editor/script/window/ScriptWindow.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar/AnimationPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar/HistoryPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar/PropertyPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar/SettingPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar/Sidebar.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar2/HierachyPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar2/ScriptPanel.js
create mode 100644 ShadowEditor.Core/src/editor/sidebar2/Sidebar.js
create mode 100644 ShadowEditor.Core/src/editor/window/AudioEditWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/AudioWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/MMDEditWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/MMDWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/ModelEditWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/ModelWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/OptionsWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/SceneEditWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/SceneWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/TextureEditWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/TextureSettingWindow.js
create mode 100644 ShadowEditor.Core/src/editor/window/TextureWindow.js
create mode 100644 ShadowEditor.Core/src/effect/BaseEffect.js
create mode 100644 ShadowEditor.Core/src/effect/SelectEffect.js
create mode 100644 ShadowEditor.Core/src/effect/shader/select_fragment.glsl
create mode 100644 ShadowEditor.Core/src/effect/shader/select_vertex.glsl
create mode 100644 ShadowEditor.Core/src/event/AnimateEvent.js
create mode 100644 ShadowEditor.Core/src/event/BaseEvent.js
create mode 100644 ShadowEditor.Core/src/event/EditorControlsEvent.js
create mode 100644 ShadowEditor.Core/src/event/EventDispatcher.js
create mode 100644 ShadowEditor.Core/src/event/EventList.js
create mode 100644 ShadowEditor.Core/src/event/KeyDownEvent.js
create mode 100644 ShadowEditor.Core/src/event/ObjectEvent.js
create mode 100644 ShadowEditor.Core/src/event/PickEvent.js
create mode 100644 ShadowEditor.Core/src/event/RenderEvent.js
create mode 100644 ShadowEditor.Core/src/event/ResizeEvent.js
create mode 100644 ShadowEditor.Core/src/event/TransformControlsEvent.js
create mode 100644 ShadowEditor.Core/src/event/menu/MenuEvent.js
create mode 100644 ShadowEditor.Core/src/event/menu/physics/AddPhysicsClothEvent.js
create mode 100644 ShadowEditor.Core/src/event/menu/physics/AddPhysicsPlaneEvent.js
create mode 100644 ShadowEditor.Core/src/event/menu/physics/AddPhysicsWallEvent.js
create mode 100644 ShadowEditor.Core/src/index.js
create mode 100644 ShadowEditor.Core/src/loader/AMFLoader.js
create mode 100644 ShadowEditor.Core/src/loader/AWDLoader.js
create mode 100644 ShadowEditor.Core/src/loader/BabylonLoader.js
create mode 100644 ShadowEditor.Core/src/loader/BaseLoader.js
create mode 100644 ShadowEditor.Core/src/loader/BinaryLoader.js
create mode 100644 ShadowEditor.Core/src/loader/CTMLoader.js
create mode 100644 ShadowEditor.Core/src/loader/ColladaLoader.js
create mode 100644 ShadowEditor.Core/src/loader/FBXLoader.js
create mode 100644 ShadowEditor.Core/src/loader/GLTFLoader.js
create mode 100644 ShadowEditor.Core/src/loader/KMZLoader.js
create mode 100644 ShadowEditor.Core/src/loader/LOLLoader.js
create mode 100644 ShadowEditor.Core/src/loader/MD2Loader.js
create mode 100644 ShadowEditor.Core/src/loader/ModelLoader.js
create mode 100644 ShadowEditor.Core/src/loader/OBJLoader.js
create mode 100644 ShadowEditor.Core/src/loader/ObjectLoader.js
create mode 100644 ShadowEditor.Core/src/loader/PLYLoader.js
create mode 100644 ShadowEditor.Core/src/loader/PMDLoader.js
create mode 100644 ShadowEditor.Core/src/loader/STLLoader.js
create mode 100644 ShadowEditor.Core/src/loader/VTKLoader.js
create mode 100644 ShadowEditor.Core/src/lol/Animation.js
create mode 100644 ShadowEditor.Core/src/lol/AnimationBone.js
create mode 100644 ShadowEditor.Core/src/lol/BaseAnimations.js
create mode 100644 ShadowEditor.Core/src/lol/Bone.js
create mode 100644 ShadowEditor.Core/src/lol/Constant.js
create mode 100644 ShadowEditor.Core/src/lol/DataView2.js
create mode 100644 ShadowEditor.Core/src/lol/HiddenBones.js
create mode 100644 ShadowEditor.Core/src/lol/Model.js
create mode 100644 ShadowEditor.Core/src/lol/Texture.js
create mode 100644 ShadowEditor.Core/src/lol/Vertex.js
create mode 100644 ShadowEditor.Core/src/object/component/Fire.js
create mode 100644 ShadowEditor.Core/src/object/component/ParticleEmitter.js
create mode 100644 ShadowEditor.Core/src/object/component/Sky.js
create mode 100644 ShadowEditor.Core/src/object/component/Smoke.js
create mode 100644 ShadowEditor.Core/src/object/component/shader/smoke_fragment.glsl
create mode 100644 ShadowEditor.Core/src/object/component/shader/smoke_vertex.glsl
create mode 100644 ShadowEditor.Core/src/object/geometry/Box.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Circle.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Cylinder.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Group.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Icosahedron.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Lathe.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Plane.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Sphere.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Sprite.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Teapot.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Text.js
create mode 100644 ShadowEditor.Core/src/object/geometry/Torus.js
create mode 100644 ShadowEditor.Core/src/object/geometry/TorusKnot.js
create mode 100644 ShadowEditor.Core/src/object/light/HemisphereLight.js
create mode 100644 ShadowEditor.Core/src/object/light/PhysicalLight.js
create mode 100644 ShadowEditor.Core/src/object/light/PointLight.js
create mode 100644 ShadowEditor.Core/src/object/light/RectAreaLight.js
create mode 100644 ShadowEditor.Core/src/object/light/shader/hemisphere_fragment.glsl
create mode 100644 ShadowEditor.Core/src/object/light/shader/hemisphere_vertex.glsl
create mode 100644 ShadowEditor.Core/src/object/terrain/PerlinTerrain.js
create mode 100644 ShadowEditor.Core/src/object/terrain/PhysicsTerrain.js
create mode 100644 ShadowEditor.Core/src/object/terrain/ShaderTerrain.js
create mode 100644 ShadowEditor.Core/src/object/terrain/shader/height_fragment.glsl
create mode 100644 ShadowEditor.Core/src/object/terrain/shader/height_vertex.glsl
create mode 100644 ShadowEditor.Core/src/object/water/Water.js
create mode 100644 ShadowEditor.Core/src/object/water/shader/heightmap_fragment.glsl
create mode 100644 ShadowEditor.Core/src/object/water/shader/smooth_fragment.glsl
create mode 100644 ShadowEditor.Core/src/object/water/shader/water_vertex.glsl
create mode 100644 ShadowEditor.Core/src/physics/PhysicsData.js
create mode 100644 ShadowEditor.Core/src/physics/PlysicsUtils.js
create mode 100644 ShadowEditor.Core/src/player/Player.js
create mode 100644 ShadowEditor.Core/src/player/animator/MMDAnimator.js
create mode 100644 ShadowEditor.Core/src/player/animator/ParticleAnimator.js
create mode 100644 ShadowEditor.Core/src/player/animator/TweenAnimator.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerAnimation.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerAudio.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerComponent.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerEvent.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerLoader.js
create mode 100644 ShadowEditor.Core/src/player/component/PlayerPhysics.js
create mode 100644 ShadowEditor.Core/src/polyfills.js
create mode 100644 ShadowEditor.Core/src/serialization/BaseSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/Converter.js
create mode 100644 ShadowEditor.Core/src/serialization/Metadata.js
create mode 100644 ShadowEditor.Core/src/serialization/app/AnimationSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/app/OptionsSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/app/ScriptSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/audio/AudioListenerSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/audio/AudioSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/camera/CameraSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/camera/CamerasSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/camera/OrthographicCameraSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/camera/PerspectiveCameraSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/BoneSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/GroupSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/MeshSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/Object3DSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/SceneSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/ServerObject.js
create mode 100644 ShadowEditor.Core/src/serialization/core/SpriteSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/WebGLRenderTargetSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/WebGLRendererSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/core/WebGLShadowMapSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/BoxBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/BufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/CircleBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/ConeBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/CylinderBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/DodecahedronBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/ExtrudeBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/GeometriesSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/GeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/IcosahedronBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/InstancedBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/LatheBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/OctahedronBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/ParametricBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/PlaneBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/PolyhedronBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/RingBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/ShapeBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/SphereBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TeapotBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TetrahedronBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TextBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TorusBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TorusKnotBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/geometry/TubeBufferGeometrySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/AmbientLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/DirectionalLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/HemisphereLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/LightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/PointLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/RectAreaLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/SpotLightSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/shadow/DirectionalLightShadowSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/shadow/LightShadowSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/shadow/LightShadowsSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/light/shadow/SpotLightShadowSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/LineBasicMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/LineDashedMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MaterialsSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshBasicMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshDepthMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshDistanceMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshFaceMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshLambertMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshNormalMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshPhongMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshPhysicalMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshStandardMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MeshToonMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/MultiMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/ParticleBasicMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/ParticleSystemMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/PointCloudMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/PointsMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/RawShaderMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/ShaderMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/ShadowMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/SpriteCanvasMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/material/SpriteMaterialSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/FireSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/ParticleEmitterSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/PerlinTerrainSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/ReflectorSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/SkySerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/objects/SmokeSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/CanvasTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/CompressedTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/CubeTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/DataTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/DepthTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/TextureSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/TexturesSerializer.js
create mode 100644 ShadowEditor.Core/src/serialization/texture/VideoTextureSerializer.js
create mode 100644 ShadowEditor.Core/src/third_party.js
create mode 100644 ShadowEditor.Core/src/ui/ImageListWindow.js
create mode 100644 ShadowEditor.Core/src/ui/ImageUploader.js
create mode 100644 ShadowEditor.Core/src/ui/Outliner.js
create mode 100644 ShadowEditor.Core/src/utils/Ajax.js
create mode 100644 ShadowEditor.Core/src/utils/Converter.js
create mode 100644 ShadowEditor.Core/src/utils/CssUtils.js
create mode 100644 ShadowEditor.Core/src/utils/GeometryUtils.js
create mode 100644 ShadowEditor.Core/src/utils/ImageUtils.js
create mode 100644 ShadowEditor.Core/src/utils/JsUtils.js
create mode 100644 ShadowEditor.Core/src/utils/MIMETypeUtils.js
create mode 100644 ShadowEditor.Core/src/utils/MathUtils.js
create mode 100644 ShadowEditor.Core/src/utils/Socket.js
create mode 100644 ShadowEditor.Core/src/utils/StringUtils.js
create mode 100644 ShadowEditor.Core/src/utils/UploadUtils.js
create mode 100644 ShadowEditor.Core/src/worker/Worker.js
diff --git a/.gitignore b/.gitignore
index e3907a95..7ffd24e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,3 +111,4 @@ typings/
/ShadowEditor.ScriptEditor/obj
/ShadowEditor.ScriptEditor/ShadowEditor.ScriptEditor.csproj.user
/ShadowEditor.ScriptEditor/dist
+/ShadowEditor.Core/dist
diff --git a/ShadowEditor.Core/README.md b/ShadowEditor.Core/README.md
new file mode 100644
index 00000000..c890a722
--- /dev/null
+++ b/ShadowEditor.Core/README.md
@@ -0,0 +1,7 @@
+# ShadowEditor.Core
+
+包管理器,提供包的管理和动态加载功能,避免开始加载资源过多,导致载入缓慢。
+
+## 依赖项
+
+[ShadowEditor.PackageManager](../ShadowEditor.PackageManager/): ShadowEditor包管理器。
\ No newline at end of file
diff --git a/ShadowEditor.Core/index.html b/ShadowEditor.Core/index.html
new file mode 100644
index 00000000..e836923b
--- /dev/null
+++ b/ShadowEditor.Core/index.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+ Shadow Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ShadowEditor.Core/rollup.config.js b/ShadowEditor.Core/rollup.config.js
new file mode 100644
index 00000000..5f4870bd
--- /dev/null
+++ b/ShadowEditor.Core/rollup.config.js
@@ -0,0 +1,42 @@
+import resolve from 'rollup-plugin-node-resolve';
+
+function glsl() {
+ return {
+ transform(code, id) {
+ if (/\.glsl$/.test(id) === false) return;
+
+ var transformedCode = 'export default ' + JSON.stringify(
+ code
+ .replace(/[ \t]*\/\/.*\n/g, '') // remove //
+ .replace(/[ \t]*\/\*[\s\S]*?\*\//g, '') // remove /* */
+ .replace(/\n{2,}/g, '\n') // # \n+ to \n
+ ) + ';';
+ return {
+ code: transformedCode,
+ map: {
+ mappings: ''
+ }
+ };
+ }
+ };
+}
+
+export default {
+ input: 'ShadowEditor.Core/src/index.js',
+ output: {
+ indent: '\t',
+ format: 'umd',
+ name: 'Shadow',
+ file: 'ShadowEditor.Core/dist/ShadowEditor.Core.js'
+ },
+ treeshake: true,
+ external: [],
+ plugins: [
+ glsl(),
+ resolve({
+ customResolveOptions: {
+ moduleDirectory: 'node_modules'
+ }
+ })
+ ]
+};
diff --git a/ShadowEditor.Core/src/Application.js b/ShadowEditor.Core/src/Application.js
new file mode 100644
index 00000000..d58f8a07
--- /dev/null
+++ b/ShadowEditor.Core/src/Application.js
@@ -0,0 +1,151 @@
+import Options from './Options';
+
+import { UI } from './third_party';
+import EventDispatcher from './event/EventDispatcher';
+
+import Menubar from './editor/menubar/Menubar';
+import Toolbar from './editor/Toolbar';
+import Viewport from './editor/Viewport';
+import Sidebar from './editor/sidebar/Sidebar';
+import Sidebar2 from './editor/sidebar2/Sidebar';
+import BottomPanel from './editor/bottom/BottomPanel';
+import StatusBar from './editor/StatusBar';
+import ScriptEditor from './editor/script/ScriptEditor';
+import Player from './player/Player';
+
+import Editor from './editor/Editor';
+import Physics from './editor/Physics';
+
+import API from './api/API';
+
+/**
+ * 应用程序
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Application(container, options) {
+
+ // 容器
+ this.container = container;
+ this.width = this.container.clientWidth;
+ this.height = this.container.clientHeight;
+
+ // 配置
+ this.options = new Options(options);
+
+ // 事件
+ this.event = new EventDispatcher(this);
+ this.call = this.event.call.bind(this.event);
+ this.on = this.event.on.bind(this.event);
+
+ var params = { app: this };
+
+ // 用户界面
+ this.ui = UI;
+ this.menubar = new Menubar(params); // 菜单栏
+ this.toolbar = new Toolbar(params); // 工具栏
+ this.viewport = new Viewport(params); // 场景编辑区
+ this.sidebar = new Sidebar(params); // 侧边栏
+ this.sidebar2 = new Sidebar2(params); // 侧边栏2
+ this.bottomPanel = new BottomPanel(params); // 底部面板
+ this.statusBar = new StatusBar(params); // 状态栏
+ this.script = new ScriptEditor(params); // 脚本编辑器
+ this.player = new Player(params); // 播放器面板
+
+ UI.create({
+ xtype: 'container',
+ parent: this.container,
+ children: [
+ new Menubar(params), {
+ xtype: 'div',
+ style: {
+ width: '100%',
+ flex: 1,
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ children: [
+ this.toolbar, {
+ xtype: 'div',
+ style: {
+ position: 'relative',
+ flex: 1,
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ children: [{
+ xtype: 'div',
+ style: {
+ position: 'relative',
+ flex: 1
+ },
+ children: [
+ this.viewport,
+ this.script,
+ this.player
+ ]
+ },
+ this.bottomPanel,
+ this.statusBar
+ ]
+ },
+ this.sidebar2,
+ this.sidebar
+ ]
+ }
+ ]
+ }).render();
+
+ // 核心
+ this.editor = new Editor(this); // 编辑器
+ this.physics = new Physics(params);
+
+ // Html5 Worker
+ // var script = document.querySelector('script[src$="ShadowEditor.js" i]').src; // http://localhost:2000/dist/ShadowEditor.js
+ // this.worker = new Worker(script);
+}
+
+// ------------------------- 程序控制 -------------------------------
+
+Application.prototype.start = function () {
+ // 启动事件 - 事件要在ui创建完成后启动
+ this.event.start();
+
+ this.call('appStart', this);
+ this.call('appStarted', this);
+
+ // 启动物体引擎
+ this.physics.start();
+
+ this.call('resize', this);
+
+ this.log('程序启动成功。');
+};
+
+Application.prototype.stop = function () {
+ this.call('appStop', this);
+ this.call('appStoped', this);
+
+ this.log('程序已经停止');
+
+ this.event.stop();
+};
+
+// ----------------------- 记录日志 --------------------------------
+
+Application.prototype.log = function (content) { // 普通日志
+ this.call('log', this, content);
+};
+
+Application.prototype.warn = function (content) { // 警告日志
+ this.call('log', this, content, 'warn');
+};
+
+Application.prototype.error = function (content) { // 错误日志
+ this.call('log', this, content, 'error');
+};
+
+// API
+Object.assign(Application.prototype, API.prototype);
+
+export default Application;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/Options.js b/ShadowEditor.Core/src/Options.js
new file mode 100644
index 00000000..44c76763
--- /dev/null
+++ b/ShadowEditor.Core/src/Options.js
@@ -0,0 +1,26 @@
+/**
+ * 配置选项
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options 配置选项
+ */
+function Options(options) {
+ options = options || {};
+
+ // 服务器配置
+ this.server = options.server === undefined ? location.origin : options.server; // 服务端地址
+
+ // 外观配置
+ this.theme = options.theme || 'assets/css/light.css'; // 皮肤
+
+ // 帮助器配置
+ this.showGrid = true; // 是否显示网格
+ this.showCameraHelper = true; // 是否显示相机帮助器
+ this.showPointLightHelper = false; // 是否显示点光源帮助器
+ this.showDirectionalLightHelper = true; // 是否显示平行光帮助器
+ this.showSpotLightHelper = true; // 是否显示聚光灯帮助器
+ this.showHemisphereLightHelper = true; // 是否显示半球光帮助器
+ this.showRectAreaLightHelper = true; // 是否显示矩形光帮助器
+ this.showSkeletonHelper = false; // 是否显示骨骼帮助器
+}
+
+export default Options;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/animation/Animation.js b/ShadowEditor.Core/src/animation/Animation.js
new file mode 100644
index 00000000..c14d52db
--- /dev/null
+++ b/ShadowEditor.Core/src/animation/Animation.js
@@ -0,0 +1,48 @@
+import AnimationType from './AnimationType';
+
+var ID = -1;
+
+/**
+ * 动画数据
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options 选项
+ */
+function Animation(options) {
+ options = options || {};
+
+ // 基本信息
+ this.id = options.id || null; // MongoDB _id字段
+ this.uuid = options.uuid || THREE.Math.generateUUID(); // uuid
+ this.name = options.name || `动画${ID--}`; // 动画名称
+ this.target = options.target || null; // 动画对象uuid
+ this.type = options.type || AnimationType.Tween; // 动画类型
+ this.beginTime = options.beginTime || 0; // 开始时间(秒)
+ this.endTime = options.endTime || 10; // 结束时间(秒)
+
+ // 补间动画
+ this.beginStatus = options.beginStatus || 'Current'; // 开始状态(Current、Custom)
+ this.beginPositionX = options.beginPositionX || 0;
+ this.beginPositionY = options.beginPositionY || 0;
+ this.beginPositionZ = options.beginPositionZ || 0;
+ this.beginRotationX = options.beginRotationX || 0;
+ this.beginRotationY = options.beginRotationY || 0;
+ this.beginRotationZ = options.beginRotationZ || 0;
+ this.beginScaleLock = options.beginScaleLock === undefined ? true : options.beginScaleLock;
+ this.beginScaleX = options.beginScaleX || 1.0;
+ this.beginScaleY = options.beginScaleY || 1.0;
+ this.beginScaleZ = options.beginScaleZ || 1.0;
+ this.ease = options.ease || 'linear'; // linear, quadIn, quadOut, quadInOut, cubicIn, cubicOut, cubicInOut, quartIn, quartOut, quartInOut, quintIn, quintOut, quintInOut, sineIn, sineOut, sineInOut, backIn, backOut, backInOut, circIn, circOut, circInOut, bounceIn, bounceOut, bounceInOut, elasticIn, elasticOut, elasticInOut
+ this.endStatus = options.endStatus || 'Current';
+ this.endPositionX = options.endPositionX || 0;
+ this.endPositionY = options.endPositionY || 0;
+ this.endPositionZ = options.endPositionZ || 0;
+ this.endRotationX = options.endRotationX || 0;
+ this.endRotationY = options.endRotationY || 0;
+ this.endRotationZ = options.endRotationZ || 0;
+ this.endScaleLock = options.endScaleLock === undefined ? true : options.endScaleLock;
+ this.endScaleX = options.endScaleX || 1.0;
+ this.endScaleY = options.endScaleY || 1.0;
+ this.endScaleZ = options.endScaleZ || 1.0;
+}
+
+export default Animation;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/animation/AnimationGroup.js b/ShadowEditor.Core/src/animation/AnimationGroup.js
new file mode 100644
index 00000000..6059113f
--- /dev/null
+++ b/ShadowEditor.Core/src/animation/AnimationGroup.js
@@ -0,0 +1,57 @@
+import Animation from './Animation';
+
+var ID = 1;
+
+/**
+ * 动画组
+ * @param {*} options 选项
+ */
+function AnimationGroup(options) {
+ options = options || {};
+ this.id = options.id || null; // MongoDB _id字段
+ this.uuid = options.uuid || THREE.Math.generateUUID(); // uuid
+ this.name = options.name || `组-${ID++}`; // 组名
+ this.type = 'AnimationGroup'; // 组类型
+ this.index = options.index || ID; // 组序号
+ this.animations = options.animations || []; // 组动画
+}
+
+/**
+ * 添加
+ * @param {*} animation
+ */
+AnimationGroup.prototype.add = function (animation) {
+ this.insert(animation, this.animations.length - 1);
+};
+
+/**
+ * 插入
+ * @param {*} animation
+ * @param {*} index
+ */
+AnimationGroup.prototype.insert = function (animation, index = 0) {
+ if (!(animation instanceof Animation)) {
+ console.warn(`AnimationGroup: animation不是Animation的实例。`);
+ return;
+ }
+ this.animations.splice(index, 0, animation);
+};
+
+/**
+ * 移除
+ * @param {*} animation
+ */
+AnimationGroup.prototype.remove = function (animation) {
+ var index = this.animations.indexOf(animation);
+ this.removeAt(index);
+};
+
+/**
+ * 移除
+ * @param {*} index
+ */
+AnimationGroup.prototype.removeAt = function (index) {
+ this.animations.splice(index, 1);
+};
+
+export default AnimationGroup;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/animation/AnimationManager.js b/ShadowEditor.Core/src/animation/AnimationManager.js
new file mode 100644
index 00000000..fa8e71e3
--- /dev/null
+++ b/ShadowEditor.Core/src/animation/AnimationManager.js
@@ -0,0 +1,98 @@
+import AnimationGroup from './AnimationGroup';
+
+/**
+ * 动画管理器
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app 应用程序
+ */
+function AnimationManager(app) {
+ this.app = app;
+ this.animations = [];
+};
+
+/**
+ * 清空所有
+ */
+AnimationManager.prototype.clear = function () {
+ this.animations = [];
+
+ for (var i = 0; i < 3; i++) {
+ var group = new AnimationGroup({
+ name: `组${i + 1}`,
+ index: i
+ });
+ this.animations.push(group);
+ }
+};
+
+/**
+ * 添加动画组
+ * @param {*} group 动画组
+ */
+AnimationManager.prototype.add = function (group) {
+ this.insert(group, this.animations.length);
+};
+
+/**
+ * 插入动画组
+ * @param {*} group 动画组
+ * @param {*} index 索引
+ */
+AnimationManager.prototype.insert = function (group, index = 0) {
+ if (!(group instanceof AnimationGroup)) {
+ console.warn(`AnimationManager: group不是AnimationGroup的实例。`);
+ return;
+ }
+ this.animations.splice(index, 0, group);
+};
+
+/**
+ * 删除组
+ * @param {*} group 动画组
+ */
+AnimationManager.prototype.remove = function (group) {
+ var index = this.animations.indexOf(group);
+ if (index > -1) {
+ this.animations.splice(index, 1);
+ }
+};
+
+/**
+ * 根据uuid删除组
+ * @param {*} uuid
+ */
+AnimationManager.prototype.removeByUUID = function (uuid) {
+ var index = this.animations.findIndex(n => n.uuid === uuid);
+ if (index > -1) {
+ this.animations.splice(index, 1);
+ }
+};
+
+/**
+ * 获取动画
+ */
+AnimationManager.prototype.getAnimationGroups = function () {
+ return this.animations;
+};
+
+/**
+ * 设置动画
+ * @param {*} animations
+ */
+AnimationManager.prototype.setAnimationGroups = function (animations) {
+ this.animations = animations;
+};
+
+/**
+ * 根据uuid获取动画
+ * @param {*} uuid
+ */
+AnimationManager.prototype.getAnimationByUUID = function (uuid) {
+ var group = this.animations.filter(n => n.animations.findIndex(m => m.uuid === uuid) > -1)[0];
+ if (group === undefined) {
+ return null;
+ }
+ return group.animations.filter(n => n.uuid === uuid)[0];
+};
+
+export default AnimationManager;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/animation/AnimationType.js b/ShadowEditor.Core/src/animation/AnimationType.js
new file mode 100644
index 00000000..35533645
--- /dev/null
+++ b/ShadowEditor.Core/src/animation/AnimationType.js
@@ -0,0 +1,22 @@
+/**
+ * 动画类型
+ * @author tengge / https://github.com/tengge1
+ */
+var AnimationType = {
+ // 补间动画
+ Tween: 'Tween',
+
+ // 骨骼动画
+ Skeletal: 'Skeletal',
+
+ // 音频播放
+ Audio: 'Audio',
+
+ // 滤镜动画
+ Filter: 'Filter',
+
+ // 粒子动画
+ Particle: 'Particle'
+};
+
+export default AnimationType;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/animation/Ease.js b/ShadowEditor.Core/src/animation/Ease.js
new file mode 100644
index 00000000..f378643a
--- /dev/null
+++ b/ShadowEditor.Core/src/animation/Ease.js
@@ -0,0 +1,456 @@
+/*
+* Ease
+* Visit http://createjs.com/ for documentation, updates and examples.
+*
+* Copyright (c) 2010 gskinner.com, inc.
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use,
+* copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following
+* conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+* OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/**
+ * @module TweenJS
+ * @author tweenjs / https://www.createjs.com/tweenjs
+ * @link https://github.com/CreateJS/TweenJS/blob/master/src/tweenjs/Ease.js
+ */
+
+/**
+ * The Ease class provides a collection of easing functions for use with TweenJS. It does not use the standard 4 param
+ * easing signature. Instead it uses a single param which indicates the current linear ratio (0 to 1) of the tween.
+ *
+ * Most methods on Ease can be passed directly as easing functions:
+ *
+ * createjs.Tween.get(target).to({x:100}, 500, createjs.Ease.linear);
+ *
+ * However, methods beginning with "get" will return an easing function based on parameter values:
+ *
+ * createjs.Tween.get(target).to({y:200}, 500, createjs.Ease.getPowIn(2.2));
+ *
+ * Please see the spark table demo for an
+ * overview of the different ease types on TweenJS.com.
+ *
+ * Equations derived from work by Robert Penner.
+ * @class Ease
+ * @static
+ **/
+function Ease() {
+ throw "Ease cannot be instantiated.";
+}
+
+
+// static methods and properties
+/**
+ * @method linear
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.linear = function (t) { return t; };
+
+/**
+ * Identical to linear.
+ * @method none
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.none = Ease.linear;
+
+/**
+ * Mimics the simple -100 to 100 easing in Adobe Flash/Animate.
+ * @method get
+ * @param {Number} amount A value from -1 (ease in) to 1 (ease out) indicating the strength and direction of the ease.
+ * @static
+ * @return {Function}
+ **/
+Ease.get = function (amount) {
+ if (amount < -1) { amount = -1; }
+ else if (amount > 1) { amount = 1; }
+ return function (t) {
+ if (amount == 0) { return t; }
+ if (amount < 0) { return t * (t * -amount + 1 + amount); }
+ return t * ((2 - t) * amount + (1 - amount));
+ };
+};
+
+/**
+ * Configurable exponential ease.
+ * @method getPowIn
+ * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
+ * @static
+ * @return {Function}
+ **/
+Ease.getPowIn = function (pow) {
+ return function (t) {
+ return Math.pow(t, pow);
+ };
+};
+
+/**
+ * Configurable exponential ease.
+ * @method getPowOut
+ * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
+ * @static
+ * @return {Function}
+ **/
+Ease.getPowOut = function (pow) {
+ return function (t) {
+ return 1 - Math.pow(1 - t, pow);
+ };
+};
+
+/**
+ * Configurable exponential ease.
+ * @method getPowInOut
+ * @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
+ * @static
+ * @return {Function}
+ **/
+Ease.getPowInOut = function (pow) {
+ return function (t) {
+ if ((t *= 2) < 1) return 0.5 * Math.pow(t, pow);
+ return 1 - 0.5 * Math.abs(Math.pow(2 - t, pow));
+ };
+};
+
+/**
+ * @method quadIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quadIn = Ease.getPowIn(2);
+/**
+ * @method quadOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quadOut = Ease.getPowOut(2);
+/**
+ * @method quadInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quadInOut = Ease.getPowInOut(2);
+
+/**
+ * @method cubicIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.cubicIn = Ease.getPowIn(3);
+/**
+ * @method cubicOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.cubicOut = Ease.getPowOut(3);
+/**
+ * @method cubicInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.cubicInOut = Ease.getPowInOut(3);
+
+/**
+ * @method quartIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quartIn = Ease.getPowIn(4);
+/**
+ * @method quartOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quartOut = Ease.getPowOut(4);
+/**
+ * @method quartInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quartInOut = Ease.getPowInOut(4);
+
+/**
+ * @method quintIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quintIn = Ease.getPowIn(5);
+/**
+ * @method quintOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quintOut = Ease.getPowOut(5);
+/**
+ * @method quintInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.quintInOut = Ease.getPowInOut(5);
+
+/**
+ * @method sineIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.sineIn = function (t) {
+ return 1 - Math.cos(t * Math.PI / 2);
+};
+
+/**
+ * @method sineOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.sineOut = function (t) {
+ return Math.sin(t * Math.PI / 2);
+};
+
+/**
+ * @method sineInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.sineInOut = function (t) {
+ return -0.5 * (Math.cos(Math.PI * t) - 1);
+};
+
+/**
+ * Configurable "back in" ease.
+ * @method getBackIn
+ * @param {Number} amount The strength of the ease.
+ * @static
+ * @return {Function}
+ **/
+Ease.getBackIn = function (amount) {
+ return function (t) {
+ return t * t * ((amount + 1) * t - amount);
+ };
+};
+/**
+ * @method backIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.backIn = Ease.getBackIn(1.7);
+
+/**
+ * Configurable "back out" ease.
+ * @method getBackOut
+ * @param {Number} amount The strength of the ease.
+ * @static
+ * @return {Function}
+ **/
+Ease.getBackOut = function (amount) {
+ return function (t) {
+ return (--t * t * ((amount + 1) * t + amount) + 1);
+ };
+};
+/**
+ * @method backOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.backOut = Ease.getBackOut(1.7);
+
+/**
+ * Configurable "back in out" ease.
+ * @method getBackInOut
+ * @param {Number} amount The strength of the ease.
+ * @static
+ * @return {Function}
+ **/
+Ease.getBackInOut = function (amount) {
+ amount *= 1.525;
+ return function (t) {
+ if ((t *= 2) < 1) return 0.5 * (t * t * ((amount + 1) * t - amount));
+ return 0.5 * ((t -= 2) * t * ((amount + 1) * t + amount) + 2);
+ };
+};
+/**
+ * @method backInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.backInOut = Ease.getBackInOut(1.7);
+
+/**
+ * @method circIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.circIn = function (t) {
+ return -(Math.sqrt(1 - t * t) - 1);
+};
+
+/**
+ * @method circOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.circOut = function (t) {
+ return Math.sqrt(1 - (--t) * t);
+};
+
+/**
+ * @method circInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.circInOut = function (t) {
+ if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);
+ return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+};
+
+/**
+ * @method bounceIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.bounceIn = function (t) {
+ return 1 - Ease.bounceOut(1 - t);
+};
+
+/**
+ * @method bounceOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.bounceOut = function (t) {
+ if (t < 1 / 2.75) {
+ return (7.5625 * t * t);
+ } else if (t < 2 / 2.75) {
+ return (7.5625 * (t -= 1.5 / 2.75) * t + 0.75);
+ } else if (t < 2.5 / 2.75) {
+ return (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375);
+ } else {
+ return (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375);
+ }
+};
+
+/**
+ * @method bounceInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.bounceInOut = function (t) {
+ if (t < 0.5) return Ease.bounceIn(t * 2) * .5;
+ return Ease.bounceOut(t * 2 - 1) * 0.5 + 0.5;
+};
+
+/**
+ * Configurable elastic ease.
+ * @method getElasticIn
+ * @param {Number} amplitude
+ * @param {Number} period
+ * @static
+ * @return {Function}
+ **/
+Ease.getElasticIn = function (amplitude, period) {
+ var pi2 = Math.PI * 2;
+ return function (t) {
+ if (t == 0 || t == 1) return t;
+ var s = period / pi2 * Math.asin(1 / amplitude);
+ return -(amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period));
+ };
+};
+/**
+ * @method elasticIn
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.elasticIn = Ease.getElasticIn(1, 0.3);
+
+/**
+ * Configurable elastic ease.
+ * @method getElasticOut
+ * @param {Number} amplitude
+ * @param {Number} period
+ * @static
+ * @return {Function}
+ **/
+Ease.getElasticOut = function (amplitude, period) {
+ var pi2 = Math.PI * 2;
+ return function (t) {
+ if (t == 0 || t == 1) return t;
+ var s = period / pi2 * Math.asin(1 / amplitude);
+ return (amplitude * Math.pow(2, -10 * t) * Math.sin((t - s) * pi2 / period) + 1);
+ };
+};
+/**
+ * @method elasticOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.elasticOut = Ease.getElasticOut(1, 0.3);
+
+/**
+ * Configurable elastic ease.
+ * @method getElasticInOut
+ * @param {Number} amplitude
+ * @param {Number} period
+ * @static
+ * @return {Function}
+ **/
+Ease.getElasticInOut = function (amplitude, period) {
+ var pi2 = Math.PI * 2;
+ return function (t) {
+ var s = period / pi2 * Math.asin(1 / amplitude);
+ if ((t *= 2) < 1) return -0.5 * (amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period));
+ return amplitude * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * pi2 / period) * 0.5 + 1;
+ };
+};
+/**
+ * @method elasticInOut
+ * @param {Number} t
+ * @static
+ * @return {Number}
+ **/
+Ease.elasticInOut = Ease.getElasticInOut(1, 0.3 * 1.5);
+
+export default Ease;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/api/API.js b/ShadowEditor.Core/src/api/API.js
new file mode 100644
index 00000000..bdba938c
--- /dev/null
+++ b/ShadowEditor.Core/src/api/API.js
@@ -0,0 +1,12 @@
+import UIAPI from './UIAPI';
+
+/**
+ * 编辑器API
+ */
+function API() {
+
+}
+
+Object.assign(API.prototype, UIAPI.prototype);
+
+export default API;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/api/UIAPI.js b/ShadowEditor.Core/src/api/UIAPI.js
new file mode 100644
index 00000000..ac567e9d
--- /dev/null
+++ b/ShadowEditor.Core/src/api/UIAPI.js
@@ -0,0 +1,12 @@
+/**
+ * 用户界面API
+ */
+function UIAPI() {
+
+}
+
+UIAPI.prototype.addMenu = function () {
+ alert('添加菜单');
+};
+
+export default UIAPI;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/AddObjectCommand.js b/ShadowEditor.Core/src/command/AddObjectCommand.js
new file mode 100644
index 00000000..996aa81e
--- /dev/null
+++ b/ShadowEditor.Core/src/command/AddObjectCommand.js
@@ -0,0 +1,56 @@
+import Command from './Command';
+
+/**
+ * 添加物体命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @constructor
+ */
+function AddObjectCommand(object) {
+ Command.call(this);
+
+ this.type = 'AddObjectCommand';
+
+ this.object = object;
+
+ if (object !== undefined) {
+ this.name = '添加物体:' + object.name;
+ }
+};
+
+AddObjectCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(AddObjectCommand.prototype, {
+ constructor: AddObjectCommand,
+
+ execute: function () {
+ this.editor.addObject(this.object);
+ this.editor.select(this.object);
+ },
+
+ undo: function () {
+ this.editor.removeObject(this.object);
+ this.editor.deselect();
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+ output.object = this.object.toJSON();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.object.object.uuid);
+
+ if (this.object === undefined) {
+ var loader = new THREE.ObjectLoader();
+ this.object = loader.parse(json.object);
+ }
+ }
+});
+
+export default AddObjectCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/AddScriptCommand.js b/ShadowEditor.Core/src/command/AddScriptCommand.js
new file mode 100644
index 00000000..53d2d88b
--- /dev/null
+++ b/ShadowEditor.Core/src/command/AddScriptCommand.js
@@ -0,0 +1,64 @@
+import Command from './Command';
+
+/**
+ * 添加脚本命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @constructor
+ */
+var AddScriptCommand = function (object, script) {
+ Command.call(this);
+
+ this.type = 'AddScriptCommand';
+ this.name = '添加脚本';
+
+ this.object = object;
+ this.script = script;
+};
+
+AddScriptCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(AddScriptCommand.prototype, {
+ constructor: AddScriptCommand,
+
+ execute: function () {
+ if (this.editor.scripts[this.object.uuid] === undefined) {
+ this.editor.scripts[this.object.uuid] = [];
+ }
+
+ this.editor.scripts[this.object.uuid].push(this.script);
+ this.editor.app.call('scriptAdded', this, this.script);
+ },
+
+ undo: function () {
+ if (this.editor.scripts[this.object.uuid] === undefined) return;
+
+ var index = this.editor.scripts[this.object.uuid].indexOf(this.script);
+
+ if (index !== - 1) {
+ this.editor.scripts[this.object.uuid].splice(index, 1);
+ }
+
+ this.editor.app.call('scriptRemoved', this, this.script);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.script = this.script;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.script = json.script;
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ }
+});
+
+export default AddScriptCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/Command.js b/ShadowEditor.Core/src/command/Command.js
new file mode 100644
index 00000000..dafbca86
--- /dev/null
+++ b/ShadowEditor.Core/src/command/Command.js
@@ -0,0 +1,36 @@
+/**
+ * 命令基类
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param editorRef pointer to main editor object used to initialize each command object with a reference to the editor
+ * @constructor
+ */
+function Command(editorRef) {
+ this.id = -1;
+ this.inMemory = false;
+ this.updatable = false;
+ this.type = '';
+ this.name = '';
+
+ if (editorRef !== undefined) {
+ Command.editor = editorRef;
+ }
+ this.editor = Command.editor;
+};
+
+Command.prototype.toJSON = function () {
+ var output = {};
+ output.type = this.type;
+ output.id = this.id;
+ output.name = this.name;
+ return output;
+};
+
+Command.prototype.fromJSON = function (json) {
+ this.inMemory = true;
+ this.type = json.type;
+ this.id = json.id;
+ this.name = json.name;
+};
+
+export default Command;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/MoveObjectCommand.js b/ShadowEditor.Core/src/command/MoveObjectCommand.js
new file mode 100644
index 00000000..8c5d4386
--- /dev/null
+++ b/ShadowEditor.Core/src/command/MoveObjectCommand.js
@@ -0,0 +1,94 @@
+import Command from './Command';
+
+/**
+ * 移动物体命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newParent THREE.Object3D
+ * @param newBefore THREE.Object3D
+ * @constructor
+ */
+function MoveObjectCommand(object, newParent, newBefore) {
+ Command.call(this);
+
+ this.type = 'MoveObjectCommand';
+ this.name = '移动物体';
+
+ this.object = object;
+ this.oldParent = (object !== undefined) ? object.parent : undefined;
+ this.oldIndex = (this.oldParent !== undefined) ? this.oldParent.children.indexOf(this.object) : undefined;
+ this.newParent = newParent;
+
+ if (newBefore !== undefined) {
+ this.newIndex = (newParent !== undefined) ? newParent.children.indexOf(newBefore) : undefined;
+ } else {
+ this.newIndex = (newParent !== undefined) ? newParent.children.length : undefined;
+ }
+
+ if (this.oldParent === this.newParent && this.newIndex > this.oldIndex) {
+ this.newIndex--;
+ }
+
+ this.newBefore = newBefore;
+};
+
+MoveObjectCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(MoveObjectCommand.prototype, {
+ constructor: MoveObjectCommand,
+
+ execute: function () {
+ this.oldParent.remove(this.object);
+
+ var children = this.newParent.children;
+ children.splice(this.newIndex, 0, this.object);
+ this.object.parent = this.newParent;
+
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ this.newParent.remove(this.object);
+
+ var children = this.oldParent.children;
+ children.splice(this.oldIndex, 0, this.object);
+ this.object.parent = this.oldParent;
+
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.newParentUuid = this.newParent.uuid;
+ output.oldParentUuid = this.oldParent.uuid;
+ output.newIndex = this.newIndex;
+ output.oldIndex = this.oldIndex;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.oldParent = this.editor.objectByUuid(json.oldParentUuid);
+ if (this.oldParent === undefined) {
+
+ this.oldParent = this.editor.scene;
+
+ }
+ this.newParent = this.editor.objectByUuid(json.newParentUuid);
+ if (this.newParent === undefined) {
+
+ this.newParent = this.editor.scene;
+
+ }
+ this.newIndex = json.newIndex;
+ this.oldIndex = json.oldIndex;
+ }
+});
+
+export default MoveObjectCommand;
diff --git a/ShadowEditor.Core/src/command/MultiCmdsCommand.js b/ShadowEditor.Core/src/command/MultiCmdsCommand.js
new file mode 100644
index 00000000..863caf5d
--- /dev/null
+++ b/ShadowEditor.Core/src/command/MultiCmdsCommand.js
@@ -0,0 +1,62 @@
+import Command from './Command';
+
+/**
+ * 同时执行多种命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param cmdArray array containing command objects
+ * @constructor
+ */
+function MultiCmdsCommand(cmdArray) {
+ Command.call(this);
+
+ this.type = 'MultiCmdsCommand';
+ this.name = '多种改变';
+
+ this.cmdArray = (cmdArray !== undefined) ? cmdArray : [];
+};
+
+MultiCmdsCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(MultiCmdsCommand.prototype, {
+ constructor: MultiCmdsCommand,
+
+ execute: function () {
+ for (var i = 0; i < this.cmdArray.length; i++) {
+ this.cmdArray[i].execute();
+ }
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ for (var i = this.cmdArray.length - 1; i >= 0; i--) {
+ this.cmdArray[i].undo();
+ }
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ var cmds = [];
+ for (var i = 0; i < this.cmdArray.length; i++) {
+ cmds.push(this.cmdArray[i].toJSON());
+ }
+ output.cmds = cmds;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ var cmds = json.cmds;
+ for (var i = 0; i < cmds.length; i++) {
+ var cmd = new window[cmds[i].type](); // creates a new object of type "json.type"
+ cmd.fromJSON(cmds[i]);
+ this.cmdArray.push(cmd);
+ }
+ }
+});
+
+export default MultiCmdsCommand;
diff --git a/ShadowEditor.Core/src/command/RemoveObjectCommand.js b/ShadowEditor.Core/src/command/RemoveObjectCommand.js
new file mode 100644
index 00000000..02204243
--- /dev/null
+++ b/ShadowEditor.Core/src/command/RemoveObjectCommand.js
@@ -0,0 +1,87 @@
+import Command from './Command';
+
+/**
+ * 移除物体命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @constructor
+ */
+function RemoveObjectCommand(object) {
+ Command.call(this);
+
+ this.type = 'RemoveObjectCommand';
+ this.name = '移除物体';
+
+ this.object = object;
+
+ this.parent = (object !== undefined) ? object.parent : undefined;
+
+ if (this.parent !== undefined) {
+ this.index = this.parent.children.indexOf(this.object);
+ }
+};
+
+RemoveObjectCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(RemoveObjectCommand.prototype, {
+ constructor: RemoveObjectCommand,
+
+ execute: function () {
+ var scope = this.editor;
+ this.object.traverse(function (child) {
+
+ scope.removeHelper(child);
+
+ });
+
+ this.parent.remove(this.object);
+ this.editor.select(this.parent);
+
+ this.editor.app.call('objectRemoved', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ var scope = this.editor;
+
+ this.object.traverse(function (child) {
+ scope.addHelper(child);
+ });
+
+ this.parent.children.splice(this.index, 0, this.object);
+ this.object.parent = this.parent;
+ this.editor.select(this.object);
+
+ this.editor.app.call('objectAdded', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+ output.object = this.object.toJSON();
+ output.index = this.index;
+ output.parentUuid = this.parent.uuid;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.parent = this.editor.objectByUuid(json.parentUuid);
+ if (this.parent === undefined) {
+ this.parent = this.editor.scene;
+ }
+
+ this.index = json.index;
+
+ this.object = this.editor.objectByUuid(json.object.object.uuid);
+ if (this.object === undefined) {
+ var loader = new THREE.ObjectLoader();
+ this.object = loader.parse(json.object);
+ }
+ }
+});
+
+export default RemoveObjectCommand;
diff --git a/ShadowEditor.Core/src/command/RemoveScriptCommand.js b/ShadowEditor.Core/src/command/RemoveScriptCommand.js
new file mode 100644
index 00000000..e23d917b
--- /dev/null
+++ b/ShadowEditor.Core/src/command/RemoveScriptCommand.js
@@ -0,0 +1,68 @@
+import Command from './Command';
+
+/**
+ * 移除脚本命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @constructor
+ */
+function RemoveScriptCommand(object, script) {
+ Command.call(this);
+
+ this.type = 'RemoveScriptCommand';
+ this.name = '移除脚本';
+
+ this.object = object;
+ this.script = script;
+ if (this.object && this.script) {
+ this.index = this.editor.scripts[this.object.uuid].indexOf(this.script);
+ }
+};
+
+RemoveScriptCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(RemoveScriptCommand.prototype, {
+ constructor: RemoveScriptCommand,
+
+ execute: function () {
+ if (this.editor.scripts[this.object.uuid] === undefined) return;
+
+ if (this.index !== - 1) {
+ this.editor.scripts[this.object.uuid].splice(this.index, 1);
+ }
+
+ this.editor.app.call('scriptRemoved', this, this.script);
+ },
+
+ undo: function () {
+ if (this.editor.scripts[this.object.uuid] === undefined) {
+ this.editor.scripts[this.object.uuid] = [];
+ }
+
+ this.editor.scripts[this.object.uuid].splice(this.index, 0, this.script);
+
+ this.editor.app.call('scriptAdded', this, this.script);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.script = this.script;
+ output.index = this.index;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.script = json.script;
+ this.index = json.index;
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ }
+});
+
+export default RemoveScriptCommand;
diff --git a/ShadowEditor.Core/src/command/SetColorCommand.js b/ShadowEditor.Core/src/command/SetColorCommand.js
new file mode 100644
index 00000000..b405f31d
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetColorCommand.js
@@ -0,0 +1,65 @@
+import Command from './Command';
+
+/**
+ * 设置颜色命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue integer representing a hex color value
+ * @constructor
+ */
+function SetColorCommand(object, attributeName, newValue) {
+ Command.call(this);
+
+ this.type = 'SetColorCommand';
+ this.name = '设置 ' + attributeName;
+ this.updatable = true;
+
+ this.object = object;
+ this.attributeName = attributeName;
+ this.oldValue = (object !== undefined) ? this.object[this.attributeName].getHex() : undefined;
+ this.newValue = newValue;
+};
+
+SetColorCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetColorCommand.prototype, {
+ constructor: SetColorCommand,
+
+ execute: function () {
+ this.object[this.attributeName].setHex(this.newValue);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ undo: function () {
+ this.object[this.attributeName].setHex(this.oldValue);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ update: function (cmd) {
+ this.newValue = cmd.newValue;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.attributeName = json.attributeName;
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ }
+});
+
+export default SetColorCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetGeometryCommand.js b/ShadowEditor.Core/src/command/SetGeometryCommand.js
new file mode 100644
index 00000000..e68779e1
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetGeometryCommand.js
@@ -0,0 +1,75 @@
+import Command from './Command';
+
+/**
+ * 设置几何体命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newGeometry THREE.Geometry
+ * @constructor
+ */
+function SetGeometryCommand(object, newGeometry) {
+ Command.call(this);
+
+ this.type = 'SetGeometryCommand';
+ this.name = '设置几何体';
+ this.updatable = true;
+
+ this.object = object;
+ this.oldGeometry = (object !== undefined) ? object.geometry : undefined;
+ this.newGeometry = newGeometry;
+};
+
+SetGeometryCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetGeometryCommand.prototype, {
+ constructor: SetGeometryCommand,
+
+ execute: function () {
+ this.object.geometry.dispose();
+ this.object.geometry = this.newGeometry;
+ this.object.geometry.computeBoundingSphere();
+
+ this.editor.app.call('geometryChanged', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ this.object.geometry.dispose();
+ this.object.geometry = this.oldGeometry;
+ this.object.geometry.computeBoundingSphere();
+
+ this.editor.app.call('geometryChanged', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ update: function (cmd) {
+ this.newGeometry = cmd.newGeometry;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.oldGeometry = this.object.geometry.toJSON();
+ output.newGeometry = this.newGeometry.toJSON();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+
+ this.oldGeometry = parseGeometry(json.oldGeometry);
+ this.newGeometry = parseGeometry(json.newGeometry);
+
+ function parseGeometry(data) {
+ var loader = new THREE.ObjectLoader();
+ return loader.parseGeometries([data])[data.uuid];
+ }
+ }
+});
+
+export default SetGeometryCommand;
diff --git a/ShadowEditor.Core/src/command/SetGeometryValueCommand.js b/ShadowEditor.Core/src/command/SetGeometryValueCommand.js
new file mode 100644
index 00000000..c9b7f68f
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetGeometryValueCommand.js
@@ -0,0 +1,64 @@
+import Command from './Command';
+
+/**
+ * 设置几何体值命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+function SetGeometryValueCommand(object, attributeName, newValue) {
+ Command.call(this);
+
+ this.type = 'SetGeometryValueCommand';
+ this.name = '设置几何体.' + attributeName;
+
+ this.object = object;
+ this.attributeName = attributeName;
+ this.oldValue = (object !== undefined) ? object.geometry[attributeName] : undefined;
+ this.newValue = newValue;
+};
+
+SetGeometryValueCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetGeometryValueCommand.prototype, {
+ constructor: SetGeometryValueCommand,
+
+ execute: function () {
+ this.object.geometry[this.attributeName] = this.newValue;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('geometryChanged', this);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ this.object.geometry[this.attributeName] = this.oldValue;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('geometryChanged', this);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.attributeName = json.attributeName;
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ }
+});
+
+export default SetGeometryValueCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetMaterialColorCommand.js b/ShadowEditor.Core/src/command/SetMaterialColorCommand.js
new file mode 100644
index 00000000..ac25f775
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetMaterialColorCommand.js
@@ -0,0 +1,65 @@
+import Command from './Command';
+
+/**
+ * 设置材质颜色命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue integer representing a hex color value
+ * @constructor
+ */
+function SetMaterialColorCommand(object, attributeName, newValue) {
+ Command.call(this);
+
+ this.type = 'SetMaterialColorCommand';
+ this.name = '设置材质.' + attributeName;
+ this.updatable = true;
+
+ this.object = object;
+ this.attributeName = attributeName;
+ this.oldValue = (object !== undefined) ? this.object.material[this.attributeName].getHex() : undefined;
+ this.newValue = newValue;
+};
+
+SetMaterialColorCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetMaterialColorCommand.prototype, {
+ constructor: SetMaterialColorCommand,
+
+ execute: function () {
+ this.object.material[this.attributeName].setHex(this.newValue);
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ undo: function () {
+ this.object.material[this.attributeName].setHex(this.oldValue);
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ update: function (cmd) {
+ this.newValue = cmd.newValue;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.attributeName = json.attributeName;
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ }
+});
+
+export default SetMaterialColorCommand;
diff --git a/ShadowEditor.Core/src/command/SetMaterialCommand.js b/ShadowEditor.Core/src/command/SetMaterialCommand.js
new file mode 100644
index 00000000..68505aee
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetMaterialCommand.js
@@ -0,0 +1,64 @@
+import Command from './Command';
+
+/**
+ * 设置材质命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newMaterial THREE.Material
+ * @constructor
+ */
+function SetMaterialCommand(object, newMaterial) {
+ Command.call(this);
+
+ this.type = 'SetMaterialCommand';
+ this.name = '新材质';
+
+ this.object = object;
+ this.oldMaterial = (object !== undefined) ? object.material : undefined;
+ this.newMaterial = newMaterial;
+};
+
+SetMaterialCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetMaterialCommand.prototype, {
+ constructor: SetMaterialCommand,
+
+ execute: function () {
+ this.object.material = this.newMaterial;
+ this.editor.app.call('materialChanged', this, this.newMaterial);
+ },
+
+ undo: function () {
+ this.object.material = this.oldMaterial;
+ this.editor.app.call('materialChanged', this, this.oldMaterial);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.oldMaterial = this.oldMaterial.toJSON();
+ output.newMaterial = this.newMaterial.toJSON();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.oldMaterial = parseMaterial(json.oldMaterial);
+ this.newMaterial = parseMaterial(json.newMaterial);
+
+ function parseMaterial(json) {
+ var loader = new THREE.ObjectLoader();
+ var images = loader.parseImages(json.images);
+ var textures = loader.parseTextures(json.textures, images);
+ var materials = loader.parseMaterials([json], textures);
+ return materials[json.uuid];
+ }
+ }
+});
+
+export default SetMaterialCommand;
diff --git a/ShadowEditor.Core/src/command/SetMaterialMapCommand.js b/ShadowEditor.Core/src/command/SetMaterialMapCommand.js
new file mode 100644
index 00000000..9cb9f9fe
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetMaterialMapCommand.js
@@ -0,0 +1,113 @@
+import Command from './Command';
+
+/**
+ * 设置材质纹理命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param mapName string
+ * @param newMap THREE.Texture
+ * @constructor
+ */
+function SetMaterialMapCommand(object, mapName, newMap) {
+ Command.call(this);
+
+ this.type = 'SetMaterialMapCommand';
+ this.name = '设置材质.' + mapName;
+
+ this.object = object;
+ this.mapName = mapName;
+ this.oldMap = (object !== undefined) ? object.material[mapName] : undefined;
+ this.newMap = newMap;
+};
+
+SetMaterialMapCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetMaterialMapCommand.prototype, {
+ constructor: SetMaterialMapCommand,
+
+ execute: function () {
+ this.object.material[this.mapName] = this.newMap;
+ this.object.material.needsUpdate = true;
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ undo: function () {
+ this.object.material[this.mapName] = this.oldMap;
+ this.object.material.needsUpdate = true;
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.mapName = this.mapName;
+ output.newMap = serializeMap(this.newMap);
+ output.oldMap = serializeMap(this.oldMap);
+
+ return output;
+
+ // serializes a map (THREE.Texture)
+
+ function serializeMap(map) {
+ if (map === null || map === undefined) return null;
+
+ var meta = {
+ geometries: {},
+ materials: {},
+ textures: {},
+ images: {}
+ };
+
+ var json = map.toJSON(meta);
+ var images = extractFromCache(meta.images);
+ if (images.length > 0) json.images = images;
+ json.sourceFile = map.sourceFile;
+
+ return json;
+ }
+
+ // Note: The function 'extractFromCache' is copied from Object3D.toJSON()
+
+ // extract data from the cache hash
+ // remove metadata on each item
+ // and return as array
+ function extractFromCache(cache) {
+ var values = [];
+ for (var key in cache) {
+
+ var data = cache[key];
+ delete data.metadata;
+ values.push(data);
+
+ }
+ return values;
+ }
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.mapName = json.mapName;
+ this.oldMap = parseTexture(json.oldMap);
+ this.newMap = parseTexture(json.newMap);
+
+ function parseTexture(json) {
+ var map = null;
+ if (json !== null) {
+
+ var loader = new THREE.ObjectLoader();
+ var images = loader.parseImages(json.images);
+ var textures = loader.parseTextures([json], images);
+ map = textures[json.uuid];
+ map.sourceFile = json.sourceFile;
+
+ }
+ return map;
+ }
+ }
+});
+
+export default SetMaterialMapCommand;
diff --git a/ShadowEditor.Core/src/command/SetMaterialValueCommand.js b/ShadowEditor.Core/src/command/SetMaterialValueCommand.js
new file mode 100644
index 00000000..37f8ae64
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetMaterialValueCommand.js
@@ -0,0 +1,69 @@
+import Command from './Command';
+
+/**
+ * 设置材质值命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+function SetMaterialValueCommand(object, attributeName, newValue) {
+ Command.call(this);
+
+ this.type = 'SetMaterialValueCommand';
+ this.name = '设置材质.' + attributeName;
+ this.updatable = true;
+
+ this.object = object;
+ this.oldValue = (object !== undefined) ? object.material[attributeName] : undefined;
+ this.newValue = newValue;
+ this.attributeName = attributeName;
+};
+
+SetMaterialValueCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetMaterialValueCommand.prototype, {
+ constructor: SetMaterialValueCommand,
+
+ execute: function () {
+ this.object.material[this.attributeName] = this.newValue;
+ this.object.material.needsUpdate = true;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ undo: function () {
+ this.object.material[this.attributeName] = this.oldValue;
+ this.object.material.needsUpdate = true;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('materialChanged', this, this.object.material);
+ },
+
+ update: function (cmd) {
+ this.newValue = cmd.newValue;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.attributeName = json.attributeName;
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ }
+});
+
+export default SetMaterialValueCommand;
diff --git a/ShadowEditor.Core/src/command/SetPositionCommand.js b/ShadowEditor.Core/src/command/SetPositionCommand.js
new file mode 100644
index 00000000..f548ef6e
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetPositionCommand.js
@@ -0,0 +1,71 @@
+import Command from './Command';
+
+/**
+ * 设置位置命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newPosition THREE.Vector3
+ * @param optionalOldPosition THREE.Vector3
+ * @constructor
+ */
+function SetPositionCommand(object, newPosition, optionalOldPosition) {
+ Command.call(this);
+
+ this.type = 'SetPositionCommand';
+ this.name = '设置位置';
+ this.updatable = true;
+
+ this.object = object;
+
+ if (object !== undefined && newPosition !== undefined) {
+ this.oldPosition = object.position.clone();
+ this.newPosition = newPosition.clone();
+ }
+
+ if (optionalOldPosition !== undefined) {
+ this.oldPosition = optionalOldPosition.clone();
+ }
+};
+
+SetPositionCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetPositionCommand.prototype, {
+ constructor: SetPositionCommand,
+
+ execute: function () {
+ this.object.position.copy(this.newPosition);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ undo: function () {
+ this.object.position.copy(this.oldPosition);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ update: function (command) {
+ this.newPosition.copy(command.newPosition);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.oldPosition = this.oldPosition.toArray();
+ output.newPosition = this.newPosition.toArray();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.oldPosition = new THREE.Vector3().fromArray(json.oldPosition);
+ this.newPosition = new THREE.Vector3().fromArray(json.newPosition);
+ }
+});
+
+export default SetPositionCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetRotationCommand.js b/ShadowEditor.Core/src/command/SetRotationCommand.js
new file mode 100644
index 00000000..3a796f07
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetRotationCommand.js
@@ -0,0 +1,71 @@
+import Command from './Command';
+
+/**
+ * 设置旋转命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newRotation THREE.Euler
+ * @param optionalOldRotation THREE.Euler
+ * @constructor
+ */
+function SetRotationCommand(object, newRotation, optionalOldRotation) {
+ Command.call(this);
+
+ this.type = 'SetRotationCommand';
+ this.name = '设置旋转';
+ this.updatable = true;
+
+ this.object = object;
+
+ if (object !== undefined && newRotation !== undefined) {
+ this.oldRotation = object.rotation.clone();
+ this.newRotation = newRotation.clone();
+ }
+
+ if (optionalOldRotation !== undefined) {
+ this.oldRotation = optionalOldRotation.clone();
+ }
+};
+
+SetRotationCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetRotationCommand.prototype, {
+ constructor: SetRotationCommand,
+
+ execute: function () {
+ this.object.rotation.copy(this.newRotation);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ undo: function () {
+ this.object.rotation.copy(this.oldRotation);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ update: function (command) {
+ this.newRotation.copy(command.newRotation);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.oldRotation = this.oldRotation.toArray();
+ output.newRotation = this.newRotation.toArray();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.oldRotation = new THREE.Euler().fromArray(json.oldRotation);
+ this.newRotation = new THREE.Euler().fromArray(json.newRotation);
+ }
+});
+
+export default SetRotationCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetScaleCommand.js b/ShadowEditor.Core/src/command/SetScaleCommand.js
new file mode 100644
index 00000000..c1db5a95
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetScaleCommand.js
@@ -0,0 +1,71 @@
+import Command from './Command';
+
+/**
+ * 设置缩放命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newScale THREE.Vector3
+ * @param optionalOldScale THREE.Vector3
+ * @constructor
+ */
+function SetScaleCommand(object, newScale, optionalOldScale) {
+ Command.call(this);
+
+ this.type = 'SetScaleCommand';
+ this.name = '设置缩放';
+ this.updatable = true;
+
+ this.object = object;
+
+ if (object !== undefined && newScale !== undefined) {
+ this.oldScale = object.scale.clone();
+ this.newScale = newScale.clone();
+ }
+
+ if (optionalOldScale !== undefined) {
+ this.oldScale = optionalOldScale.clone();
+ }
+};
+
+SetScaleCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetScaleCommand.prototype, {
+ constructor: SetScaleCommand,
+
+ execute: function () {
+ this.object.scale.copy(this.newScale);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ undo: function () {
+ this.object.scale.copy(this.oldScale);
+ this.object.updateMatrixWorld(true);
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ update: function (command) {
+ this.newScale.copy(command.newScale);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.oldScale = this.oldScale.toArray();
+ output.newScale = this.newScale.toArray();
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.oldScale = new THREE.Vector3().fromArray(json.oldScale);
+ this.newScale = new THREE.Vector3().fromArray(json.newScale);
+ }
+});
+
+export default SetScaleCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetSceneCommand.js b/ShadowEditor.Core/src/command/SetSceneCommand.js
new file mode 100644
index 00000000..ef40d543
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetSceneCommand.js
@@ -0,0 +1,78 @@
+import Command from './Command';
+import SetUuidCommand from './SetUuidCommand';
+import SetValueCommand from './SetValueCommand';
+import AddObjectCommand from './AddObjectCommand';
+
+/**
+ * 设置场景命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param scene containing children to import
+ * @constructor
+ */
+function SetSceneCommand(scene) {
+ Command.call(this);
+
+ this.type = 'SetSceneCommand';
+ this.name = '设置场景';
+
+ this.cmdArray = [];
+
+ if (scene !== undefined) {
+ this.cmdArray.push(new SetUuidCommand(this.editor.scene, scene.uuid));
+ this.cmdArray.push(new SetValueCommand(this.editor.scene, 'name', scene.name));
+ this.cmdArray.push(new SetValueCommand(this.editor.scene, 'userData', JSON.parse(JSON.stringify(scene.userData))));
+
+ while (scene.children.length > 0) {
+ var child = scene.children.pop();
+ this.cmdArray.push(new AddObjectCommand(child));
+ }
+ }
+};
+
+SetSceneCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetSceneCommand.prototype, {
+ constructor: SetSceneCommand,
+
+ execute: function () {
+ for (var i = 0; i < this.cmdArray.length; i++) {
+ this.cmdArray[i].execute();
+ }
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ for (var i = this.cmdArray.length - 1; i >= 0; i--) {
+ this.cmdArray[i].undo();
+ }
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ var cmds = [];
+ for (var i = 0; i < this.cmdArray.length; i++) {
+
+ cmds.push(this.cmdArray[i].toJSON());
+
+ }
+ output.cmds = cmds;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ var cmds = json.cmds;
+ for (var i = 0; i < cmds.length; i++) {
+ var cmd = new window[cmds[i].type](); // creates a new object of type "json.type"
+ cmd.fromJSON(cmds[i]);
+ this.cmdArray.push(cmd);
+ }
+ }
+});
+
+export default SetSceneCommand;
diff --git a/ShadowEditor.Core/src/command/SetScriptValueCommand.js b/ShadowEditor.Core/src/command/SetScriptValueCommand.js
new file mode 100644
index 00000000..185f2e64
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetScriptValueCommand.js
@@ -0,0 +1,84 @@
+import Command from './Command';
+
+/**
+ * 设置脚本值命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @param attributeName string
+ * @param newValue string, object
+ * @param cursorPosition javascript object with format {line: 2, ch: 3}
+ * @param scrollInfo javascript object with values {left, top, width, height, clientWidth, clientHeight}
+ * @constructor
+ */
+function SetScriptValueCommand(object, script, attributeName, newValue, cursorPosition, scrollInfo) {
+ Command.call(this);
+
+ this.type = 'SetScriptValueCommand';
+ this.name = '设置脚本.' + attributeName;
+ this.updatable = true;
+
+ this.object = object;
+ this.script = script;
+
+ this.attributeName = attributeName;
+ this.oldValue = (script !== undefined) ? script[this.attributeName] : undefined;
+ this.newValue = newValue;
+ this.cursorPosition = cursorPosition;
+ this.scrollInfo = scrollInfo;
+};
+
+SetScriptValueCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetScriptValueCommand.prototype, {
+ constructor: SetScriptValueCommand,
+
+ execute: function () {
+ this.script[this.attributeName] = this.newValue;
+
+ this.editor.app.call('scriptChanged', this);
+ this.editor.app.call('refreshScriptEditor', this, this.object, this.script, this.cursorPosition, this.scrollInfo);
+ },
+
+ undo: function () {
+ this.script[this.attributeName] = this.oldValue;
+
+ this.editor.app.call('scriptChanged', this);
+ this.editor.app.call('refreshScriptEditor', this, this.object, this.script, this.cursorPosition, this.scrollInfo);
+ },
+
+ update: function (cmd) {
+ this.cursorPosition = cmd.cursorPosition;
+ this.scrollInfo = cmd.scrollInfo;
+ this.newValue = cmd.newValue;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.index = this.editor.scripts[this.object.uuid].indexOf(this.script);
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+ output.cursorPosition = this.cursorPosition;
+ output.scrollInfo = this.scrollInfo;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ this.attributeName = json.attributeName;
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ this.script = this.editor.scripts[json.objectUuid][json.index];
+ this.cursorPosition = json.cursorPosition;
+ this.scrollInfo = json.scrollInfo;
+ }
+});
+
+export default SetScriptValueCommand;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/command/SetUuidCommand.js b/ShadowEditor.Core/src/command/SetUuidCommand.js
new file mode 100644
index 00000000..f3ef5e8a
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetUuidCommand.js
@@ -0,0 +1,62 @@
+import Command from './Command';
+
+/**
+ * 设置uuid命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param newUuid string
+ * @constructor
+ */
+function SetUuidCommand(object, newUuid) {
+ Command.call(this);
+
+ this.type = 'SetUuidCommand';
+ this.name = '更新UUID';
+
+ this.object = object;
+
+ this.oldUuid = (object !== undefined) ? object.uuid : undefined;
+ this.newUuid = newUuid;
+};
+
+SetUuidCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetUuidCommand.prototype, {
+ constructor: SetUuidCommand,
+
+ execute: function () {
+ this.object.uuid = this.newUuid;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ undo: function () {
+ this.object.uuid = this.oldUuid;
+ this.editor.app.call('objectChanged', this, this.object);
+ this.editor.app.call('sceneGraphChanged', this);
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.oldUuid = this.oldUuid;
+ output.newUuid = this.newUuid;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.oldUuid = json.oldUuid;
+ this.newUuid = json.newUuid;
+ this.object = this.editor.objectByUuid(json.oldUuid);
+
+ if (this.object === undefined) {
+ this.object = this.editor.objectByUuid(json.newUuid);
+ }
+ }
+});
+
+export default SetUuidCommand;
diff --git a/ShadowEditor.Core/src/command/SetValueCommand.js b/ShadowEditor.Core/src/command/SetValueCommand.js
new file mode 100644
index 00000000..d1b536f9
--- /dev/null
+++ b/ShadowEditor.Core/src/command/SetValueCommand.js
@@ -0,0 +1,65 @@
+import Command from './Command';
+
+/**
+ * 设置值命令
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+function SetValueCommand(object, attributeName, newValue) {
+ Command.call(this);
+
+ this.type = 'SetValueCommand';
+ this.name = '设置' + attributeName;
+ this.updatable = true;
+
+ this.object = object;
+ this.attributeName = attributeName;
+ this.oldValue = (object !== undefined) ? object[attributeName] : undefined;
+ this.newValue = newValue;
+};
+
+SetValueCommand.prototype = Object.create(Command.prototype);
+
+Object.assign(SetValueCommand.prototype, {
+ constructor: SetValueCommand,
+
+ execute: function () {
+ this.object[this.attributeName] = this.newValue;
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ undo: function () {
+ this.object[this.attributeName] = this.oldValue;
+ this.editor.app.call('objectChanged', this, this.object);
+ },
+
+ update: function (cmd) {
+ this.newValue = cmd.newValue;
+ },
+
+ toJSON: function () {
+ var output = Command.prototype.toJSON.call(this);
+
+ output.objectUuid = this.object.uuid;
+ output.attributeName = this.attributeName;
+ output.oldValue = this.oldValue;
+ output.newValue = this.newValue;
+
+ return output;
+ },
+
+ fromJSON: function (json) {
+ Command.prototype.fromJSON.call(this, json);
+
+ this.attributeName = json.attributeName;
+ this.oldValue = json.oldValue;
+ this.newValue = json.newValue;
+ this.object = this.editor.objectByUuid(json.objectUuid);
+ }
+});
+
+export default SetValueCommand;
diff --git a/ShadowEditor.Core/src/component/BaseComponent.js b/ShadowEditor.Core/src/component/BaseComponent.js
new file mode 100644
index 00000000..4d27c11e
--- /dev/null
+++ b/ShadowEditor.Core/src/component/BaseComponent.js
@@ -0,0 +1,20 @@
+import { UI } from '../third_party';
+
+/**
+ * 所有组件基类
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function BaseComponent(options) {
+ UI.Control.call(this, options);
+ this.app = options.app
+}
+
+BaseComponent.prototype = Object.create(UI.Control.prototype);
+BaseComponent.prototype.constructor = BaseComponent;
+
+BaseComponent.prototype.render = function () {
+
+};
+
+export default BaseComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/BasicComponent.js b/ShadowEditor.Core/src/component/BasicComponent.js
new file mode 100644
index 00000000..e3a2e0fd
--- /dev/null
+++ b/ShadowEditor.Core/src/component/BasicComponent.js
@@ -0,0 +1,126 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+import RemoveObjectCommand from '../command/RemoveObjectCommand';
+import AddObjectCommand from '../command/AddObjectCommand';
+
+/**
+ * 基本信息组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function BasicComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+BasicComponent.prototype = Object.create(BaseComponent.prototype);
+BasicComponent.prototype.constructor = BasicComponent;
+
+BasicComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'basicPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ borderTop: 0,
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '基本信息'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id,
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.onChangeName.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'text',
+ id: 'type',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '可见性'
+ }, {
+ xtype: 'checkbox',
+ id: 'visible',
+ scope: this.id,
+ onChange: this.onChangeVisible.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+BasicComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+BasicComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+BasicComponent.prototype.updateUI = function () {
+ var container = UI.get('basicPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var name = UI.get('name', this.id);
+ var type = UI.get('type', this.id);
+ var visible = UI.get('visible', this.id);
+
+ name.setValue(this.selected.name);
+ type.setValue(this.selected.constructor.name);
+ visible.setValue(this.selected.visible);
+};
+
+BasicComponent.prototype.onChangeName = function () {
+ var name = UI.get('name', this.id);
+ var editor = this.app.editor;
+
+ editor.execute(new SetValueCommand(this.selected, 'name', name.getValue()));
+};
+
+BasicComponent.prototype.onChangeVisible = function () {
+ this.selected.visible = UI.get('visible', this.id).getValue();
+};
+
+export default BasicComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/CameraComponent.js b/ShadowEditor.Core/src/component/CameraComponent.js
new file mode 100644
index 00000000..5b6373e3
--- /dev/null
+++ b/ShadowEditor.Core/src/component/CameraComponent.js
@@ -0,0 +1,124 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 相机组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function CameraComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+CameraComponent.prototype = Object.create(BaseComponent.prototype);
+CameraComponent.prototype.constructor = CameraComponent;
+
+CameraComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'cameraPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '相机组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '视场'
+ }, {
+ xtype: 'number',
+ id: 'objectFov',
+ scope: this.id,
+ onChange: this.onSetObjectFov.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '近点'
+ }, {
+ xtype: 'number',
+ id: 'objectNear',
+ scope: this.id,
+ onChange: this.onSetObjectNear.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '远点'
+ }, {
+ xtype: 'number',
+ id: 'objectFar',
+ scope: this.id,
+ onChange: this.onSetObjectFar.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+CameraComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+CameraComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+CameraComponent.prototype.updateUI = function () {
+ var container = UI.get('cameraPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.PerspectiveCamera) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var objectFov = UI.get('objectFov', this.id);
+ var objectNear = UI.get('objectNear', this.id);
+ var objectFar = UI.get('objectFar', this.id);
+
+ objectFov.setValue(this.selected.fov);
+ objectNear.setValue(this.selected.near);
+ objectFar.setValue(this.selected.far);
+};
+
+CameraComponent.prototype.onSetObjectFov = function () {
+ var fov = UI.get('objectFov', this.id).getValue();
+ this.app.editor.execute(new SetValueCommand(this.selected, 'fov', fov));
+};
+
+CameraComponent.prototype.onSetObjectNear = function () {
+ var near = UI.get('objectNear', this.id).getValue();
+ this.app.editor.execute(new SetValueCommand(this.selected, 'near', near));
+};
+
+CameraComponent.prototype.onSetObjectFar = function () {
+ var far = UI.get('objectFar', this.id).getValue();
+ this.app.editor.execute(new SetValueCommand(this.selected, 'far', far));
+};
+
+export default CameraComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/FireComponent.js b/ShadowEditor.Core/src/component/FireComponent.js
new file mode 100644
index 00000000..66cdb9eb
--- /dev/null
+++ b/ShadowEditor.Core/src/component/FireComponent.js
@@ -0,0 +1,222 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 火焰组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function FireComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+
+ this.isPlaying = false;
+}
+
+FireComponent.prototype = Object.create(BaseComponent.prototype);
+FireComponent.prototype.constructor = FireComponent;
+
+FireComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'firePanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '火焰组件'
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'int',
+ id: 'width',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度'
+ }, {
+ xtype: 'int',
+ id: 'height',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度'
+ }, {
+ xtype: 'int',
+ id: 'depth',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '切片厚度'
+ }, {
+ xtype: 'number',
+ id: 'sliceSpacing',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label'
+ }, {
+ xtype: 'button',
+ id: 'btnPreview',
+ scope: this.id,
+ text: '预览',
+ onClick: this.onPreview.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+FireComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+FireComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+FireComponent.prototype.updateUI = function () {
+ var container = UI.get('firePanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected.userData.type === 'Fire') {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var depth = UI.get('depth', this.id);
+ var sliceSpacing = UI.get('sliceSpacing', this.id);
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ var fire = this.selected.userData.fire;
+ width.setValue(fire.mesh.userData.width);
+ height.setValue(fire.mesh.userData.height);
+ depth.setValue(fire.mesh.userData.depth);
+ sliceSpacing.setValue(fire.mesh.userData.sliceSpacing);
+
+ if (this.isPlaying) {
+ btnPreview.setText('取消');
+ } else {
+ btnPreview.setText('预览');
+ }
+};
+
+FireComponent.prototype.onChange = function () {
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var depth = UI.get('depth', this.id);
+ var sliceSpacing = UI.get('sliceSpacing', this.id);
+
+ VolumetricFire.texturePath = 'assets/textures/VolumetricFire/';
+
+ var editor = this.app.editor;
+
+ var fire = new VolumetricFire(
+ width.getValue(),
+ height.getValue(),
+ depth.getValue(),
+ sliceSpacing.getValue(),
+ editor.camera
+ );
+
+ fire.mesh.name = this.selected.name;
+ fire.mesh.position.copy(this.selected.position);
+ fire.mesh.rotation.copy(this.selected.rotation);
+ fire.mesh.scale.copy(this.selected.scale);
+
+ fire.mesh.userData.type = 'Fire';
+ fire.mesh.userData.fire = fire;
+ fire.mesh.userData.width = width.getValue();
+ fire.mesh.userData.height = height.getValue();
+ fire.mesh.userData.depth = depth.getValue();
+ fire.mesh.userData.sliceSpacing = sliceSpacing.getValue();
+
+ var index = editor.scene.children.indexOf(this.selected);
+ if (index > -1) {
+ editor.scene.children[index] = fire.mesh;
+ fire.mesh.parent = this.selected.parent;
+ this.selected.parent = null;
+ this.app.call(`objectRemoved`, this, this.selected);
+ this.app.call(`objectAdded`, this, fire.mesh);
+ editor.select(fire.mesh);
+ this.app.call('sceneGraphChanged', this.id);
+
+ fire.update(0);
+ }
+};
+
+FireComponent.prototype.onPreview = function () {
+ if (this.isPlaying) {
+ this.stopPreview();
+ } else {
+ this.startPreview();
+ }
+};
+
+FireComponent.prototype.startPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = true;
+ btnPreview.setText('取消');
+
+ this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
+};
+
+FireComponent.prototype.stopPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = false;
+ btnPreview.setText('预览');
+
+ this.app.on(`animate.${this.id}`, null);
+};
+
+FireComponent.prototype.onAnimate = function (clock, deltaTime) {
+ var elapsed = clock.getElapsedTime();
+
+ var fire = this.selected.userData.fire;
+ fire.update(elapsed);
+};
+
+export default FireComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/GeometryComponent.js b/ShadowEditor.Core/src/component/GeometryComponent.js
new file mode 100644
index 00000000..c218435a
--- /dev/null
+++ b/ShadowEditor.Core/src/component/GeometryComponent.js
@@ -0,0 +1,99 @@
+import BaseComponent from './BaseComponent';
+import PlaneGeometryComponent from './geometry/PlaneGeometryComponent';
+import BoxGeometryComponent from './geometry/BoxGeometryComponent';
+import CircleGeometryComponent from './geometry/CircleGeometryComponent';
+import CylinderGeometryComponent from './geometry/CylinderGeometryComponent';
+import SphereGeometryComponent from './geometry/SphereGeometryComponent';
+import IcosahedronGeometryComponent from './geometry/IcosahedronGeometryComponent';
+import TorusGeometryComponent from './geometry/TorusGeometryComponent';
+import TorusKnotGeometryComponent from './geometry/TorusKnotGeometryComponent';
+import LatheGeometryComponent from './geometry/LatheGeometryComponent';
+import TeapotGeometryComponent from './geometry/TeapotGeometryComponent';
+
+/**
+ * 几何体组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function GeometryComponent(options) {
+ BaseComponent.call(this, options);
+}
+
+GeometryComponent.prototype = Object.create(BaseComponent.prototype);
+GeometryComponent.prototype.constructor = GeometryComponent;
+
+GeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '几何组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'text',
+ id: 'name',
+ scope: this.id,
+ text: ''
+ }]
+ },
+ new PlaneGeometryComponent({ app: this.app }),
+ new BoxGeometryComponent({ app: this.app }),
+ new CircleGeometryComponent({ app: this.app }),
+ new CylinderGeometryComponent({ app: this.app }),
+ new SphereGeometryComponent({ app: this.app }),
+ new IcosahedronGeometryComponent({ app: this.app }),
+ new TorusGeometryComponent({ app: this.app }),
+ new TorusKnotGeometryComponent({ app: this.app }),
+ new LatheGeometryComponent({ app: this.app }),
+ new TeapotGeometryComponent({ app: this.app })
+ ]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+};
+
+GeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+GeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+
+ var name = UI.get('name', this.id);
+
+ if (editor.selected && editor.selected instanceof THREE.Mesh) {
+ container.dom.style.display = '';
+ if (editor.selected.geometry instanceof THREE.TeapotBufferGeometry) {
+ name.setValue('TeapotBufferGeometry');
+ } else {
+ name.setValue(editor.selected.geometry.constructor.name);
+ }
+ } else {
+ container.dom.style.display = 'none';
+ name.setValue('');
+ }
+};
+
+export default GeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/LMeshComponent.js b/ShadowEditor.Core/src/component/LMeshComponent.js
new file mode 100644
index 00000000..5edb098e
--- /dev/null
+++ b/ShadowEditor.Core/src/component/LMeshComponent.js
@@ -0,0 +1,158 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+import RemoveObjectCommand from '../command/RemoveObjectCommand';
+import AddObjectCommand from '../command/AddObjectCommand';
+
+/**
+ * LMesh组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function LMeshComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+
+ this.isPlaying = false;
+}
+
+LMeshComponent.prototype = Object.create(BaseComponent.prototype);
+LMeshComponent.prototype.constructor = LMeshComponent;
+
+LMeshComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'lmeshPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: 'LMesh组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '动画'
+ }, {
+ xtype: 'select',
+ id: 'anims',
+ scope: this.id,
+ onChange: this.onSelectAnim.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label'
+ }, {
+ xtype: 'button',
+ id: 'btnPreview',
+ scope: this.id,
+ text: '预览',
+ onClick: this.onPreview.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+LMeshComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+LMeshComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+LMeshComponent.prototype.updateUI = function () {
+ var container = UI.get('lmeshPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected.userData.type === 'lol') {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var anims = UI.get('anims', this.id);
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ var model = this.selected.userData.model;
+ var animNames = model.getAnimations();
+
+ var options = {
+
+ };
+
+ animNames.forEach(n => {
+ options[n] = n;
+ });
+
+ anims.setOptions(options);
+ anims.setValue(animNames[0]);
+
+ if (this.isPlaying) {
+ btnPreview.setText('取消');
+ } else {
+ btnPreview.setText('预览');
+ }
+};
+
+LMeshComponent.prototype.onSelectAnim = function () {
+ var anims = UI.get('anims', this.id);
+
+ var model = this.selected.userData.model;
+ model.setAnimation(anims.getValue());
+};
+
+LMeshComponent.prototype.onPreview = function () {
+ if (this.isPlaying) {
+ this.stopPreview();
+ } else {
+ this.startPreview();
+ }
+};
+
+LMeshComponent.prototype.startPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = true;
+ btnPreview.setText('取消');
+
+ this.onSelectAnim();
+
+ this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
+};
+
+LMeshComponent.prototype.stopPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = false;
+ btnPreview.setText('预览');
+
+ this.app.on(`animate.${this.id}`, null);
+};
+
+LMeshComponent.prototype.onAnimate = function (clock, deltaTime) {
+ var model = this.selected.userData.model;
+ model.update(clock.getElapsedTime() * 1000);
+};
+
+export default LMeshComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/LightComponent.js b/ShadowEditor.Core/src/component/LightComponent.js
new file mode 100644
index 00000000..41ab3d3e
--- /dev/null
+++ b/ShadowEditor.Core/src/component/LightComponent.js
@@ -0,0 +1,342 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 光源组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function LightComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+LightComponent.prototype = Object.create(BaseComponent.prototype);
+LightComponent.prototype.constructor = LightComponent;
+
+LightComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'lightPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '光源组件'
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '颜色'
+ }, {
+ xtype: 'color',
+ id: 'objectColor',
+ scope: this.id,
+ onChange: this.onChangeColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectIntensityRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '强度'
+ }, {
+ xtype: 'number',
+ id: 'objectIntensity',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChangeIntensity.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectDistanceRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '距离'
+ }, {
+ xtype: 'number',
+ id: 'objectDistance',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChangeDistance.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectAngleRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '角度'
+ }, {
+ xtype: 'number',
+ id: 'objectAngle',
+ scope: this.id,
+ precision: 3,
+ range: [0, Math.PI / 2],
+ onChange: this.onChangeAngle.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectPenumbraRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '半阴影'
+ }, {
+ xtype: 'number',
+ id: 'objectPenumbra',
+ scope: this.id,
+ range: [0, 1],
+ onChange: this.onChangePenumbra.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectDecayRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '衰减'
+ }, {
+ xtype: 'number',
+ id: 'objectDecay',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChangeDecay.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectSkyColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '天空颜色'
+ }, {
+ xtype: 'color',
+ id: 'objectSkyColor',
+ scope: this.id,
+ onChange: this.onChangeSkyColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectGroundColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '地面颜色'
+ }, {
+ xtype: 'color',
+ id: 'objectGroundColor',
+ scope: this.id,
+ onChange: this.onChangeGroundColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectWidthRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'number',
+ id: 'objectWidth',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChangeWidth.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectHeightRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '高度'
+ }, {
+ xtype: 'number',
+ id: 'objectHeight',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChangeHeight.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+LightComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+LightComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+LightComponent.prototype.updateUI = function () {
+ var container = UI.get('lightPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Light) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var objectColorRow = UI.get('objectColorRow', this.id);
+ var objectIntensityRow = UI.get('objectIntensityRow', this.id);
+ var objectDistanceRow = UI.get('objectDistanceRow', this.id);
+ var objectAngleRow = UI.get('objectAngleRow', this.id);
+ var objectPenumbraRow = UI.get('objectPenumbraRow', this.id);
+ var objectDecayRow = UI.get('objectDecayRow', this.id);
+ var objectSkyColorRow = UI.get('objectSkyColorRow', this.id);
+ var objectGroundColorRow = UI.get('objectGroundColorRow', this.id);
+ var objectWidthRow = UI.get('objectWidthRow', this.id);
+ var objectHeightRow = UI.get('objectHeightRow', this.id);
+
+ var objectColor = UI.get('objectColor', this.id);
+ var objectIntensity = UI.get('objectIntensity', this.id);
+ var objectDistance = UI.get('objectDistance', this.id);
+ var objectAngle = UI.get('objectAngle', this.id);
+ var objectPenumbra = UI.get('objectPenumbra', this.id);
+ var objectDecay = UI.get('objectDecay', this.id);
+ var objectSkyColor = UI.get('objectSkyColor', this.id);
+ var objectGroundColor = UI.get('objectGroundColor', this.id);
+ var objectWidth = UI.get('objectWidth', this.id);
+ var objectHeight = UI.get('objectHeight', this.id);
+
+ if (this.selected instanceof THREE.HemisphereLight) {
+ objectColorRow.dom.style.display = 'none';
+ } else {
+ objectColorRow.dom.style.display = '';
+ objectColor.setValue(`#${this.selected.color.getHexString()}`);
+ }
+
+ objectIntensityRow.dom.style.display = '';
+ objectIntensity.setValue(this.selected.intensity);
+
+ if (this.selected instanceof THREE.PointLight || this.selected instanceof THREE.SpotLight) {
+ objectDistanceRow.dom.style.display = '';
+ objectDecayRow.dom.style.display = '';
+ objectDistance.setValue(this.selected.distance);
+ objectDecay.setValue(this.selected.decay);
+ } else {
+ objectDistanceRow.dom.style.display = 'none';
+ objectDecayRow.dom.style.display = 'none';
+ }
+
+ if (this.selected instanceof THREE.SpotLight) {
+ objectAngleRow.dom.style.display = '';
+ objectPenumbraRow.dom.style.display = '';
+ objectAngle.setValue(this.selected.angle);
+ objectPenumbra.setValue(this.selected.penumbra);
+ } else {
+ objectAngleRow.dom.style.display = 'none';
+ objectPenumbraRow.dom.style.display = 'none';
+ }
+
+ if (this.selected instanceof THREE.HemisphereLight) {
+ objectSkyColorRow.dom.style.display = '';
+ objectGroundColorRow.dom.style.display = '';
+ objectSkyColor.setValue(`#${this.selected.color.getHexString()}`);
+ objectGroundColor.setValue(`#${this.selected.groundColor.getHexString()}`);
+ } else {
+ objectSkyColorRow.dom.style.display = 'none';
+ objectGroundColorRow.dom.style.display = 'none';
+ }
+
+ if (this.selected instanceof THREE.RectAreaLight) {
+ objectWidthRow.dom.style.display = '';
+ objectHeightRow.dom.style.display = '';
+ objectWidth.setValue(this.selected.width);
+ objectHeight.setValue(this.selected.height);
+ } else {
+ objectWidthRow.dom.style.display = 'none';
+ objectHeightRow.dom.style.display = 'none';
+ }
+};
+
+LightComponent.prototype.onChangeColor = function () {
+ var objectColor = UI.get('objectColor', this.id);
+ this.selected.color = new THREE.Color(objectColor.getHexValue());
+ var helper = this.selected.children.filter(n => n.userData.type === 'helper')[0];
+ if (helper) {
+ helper.material.color = this.selected.color;
+ }
+};
+
+LightComponent.prototype.onChangeIntensity = function () {
+ var objectIntensity = UI.get('objectIntensity', this.id);
+ this.selected.intensity = objectIntensity.getValue();
+};
+
+LightComponent.prototype.onChangeDistance = function () {
+ var objectDistance = UI.get('objectDistance', this.id);
+ this.selected.distance = objectDistance.getValue();
+};
+
+LightComponent.prototype.onChangeAngle = function () {
+ var objectAngle = UI.get('objectAngle', this.id);
+ this.selected.angle = objectAngle.getValue();
+};
+
+LightComponent.prototype.onChangePenumbra = function () {
+ var objectPenumbra = UI.get('objectPenumbra', this.id);
+ this.selected.penumbra = objectPenumbra.getValue();
+};
+
+LightComponent.prototype.onChangeDecay = function () {
+ var objectDecay = UI.get('objectDecay', this.id);
+ this.selected.decay = objectDecay.getValue();
+};
+
+LightComponent.prototype.onChangeSkyColor = function () {
+ var objectSkyColor = UI.get('objectSkyColor', this.id);
+ this.selected.color = new THREE.Color(objectSkyColor.getHexValue());
+
+ var sky = this.selected.children.filter(n => n.userData.type === 'sky')[0];
+ if (sky) {
+ sky.material.uniforms.topColor.value = this.selected.color;
+ }
+};
+
+LightComponent.prototype.onChangeGroundColor = function () {
+ var objectGroundColor = UI.get('objectGroundColor', this.id);
+ this.selected.groundColor = new THREE.Color(objectGroundColor.getHexValue());
+
+ var sky = this.selected.children.filter(n => n.userData.type === 'sky')[0];
+ if (sky) {
+ sky.material.uniforms.bottomColor.value = this.selected.groundColor;
+ }
+};
+
+LightComponent.prototype.onChangeWidth = function () {
+ var objectWidth = UI.get('objectWidth', this.id);
+ this.selected.width = objectWidth.getValue();
+};
+
+LightComponent.prototype.onChangeHeight = function () {
+ var objectHeight = UI.get('objectHeight', this.id);
+ this.selected.height = objectHeight.getValue();
+};
+
+export default LightComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/MMDComponent.js b/ShadowEditor.Core/src/component/MMDComponent.js
new file mode 100644
index 00000000..154d761c
--- /dev/null
+++ b/ShadowEditor.Core/src/component/MMDComponent.js
@@ -0,0 +1,114 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+import RemoveObjectCommand from '../command/RemoveObjectCommand';
+import AddObjectCommand from '../command/AddObjectCommand';
+import MMDWindow from '../editor/window/MMDWindow';
+
+/**
+ * MMD模型组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function MMDComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+MMDComponent.prototype = Object.create(BaseComponent.prototype);
+MMDComponent.prototype.constructor = MMDComponent;
+
+MMDComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'mmdPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: 'MMD模型'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '模型动画'
+ }, {
+ xtype: 'input',
+ id: 'animation',
+ scope: this.id,
+ disabled: true,
+ style: {
+ width: '80px',
+ fontSize: '12px'
+ }
+ }, {
+ xtype: 'button',
+ text: '选择',
+ onClick: this.selectAnimation.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+MMDComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+MMDComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+MMDComponent.prototype.updateUI = function () {
+ var container = UI.get('mmdPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && (editor.selected.userData.Type === 'pmd' || editor.selected.userData.Type === 'pmx')) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var animation = UI.get('animation', this.id);
+
+ if (this.selected.userData.Animation) {
+ animation.setValue(this.selected.userData.Animation.Name);
+ }
+};
+
+MMDComponent.prototype.selectAnimation = function () {
+ if (this.mmdWindow === undefined) {
+ this.mmdWindow = new MMDWindow({
+ app: this.app,
+ onSelect: this.onSelectAnimation.bind(this)
+ });
+ this.mmdWindow.render();
+ }
+ this.mmdWindow.show();
+};
+
+MMDComponent.prototype.onSelectAnimation = function (data) {
+ this.selected.userData.Animation = {};
+ Object.assign(this.selected.userData.Animation, data);
+ this.updateUI();
+};
+
+export default MMDComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/MaterialComponent.js b/ShadowEditor.Core/src/component/MaterialComponent.js
new file mode 100644
index 00000000..90d9c43a
--- /dev/null
+++ b/ShadowEditor.Core/src/component/MaterialComponent.js
@@ -0,0 +1,1443 @@
+import BaseComponent from './BaseComponent';
+import SetMaterialCommand from '../command/SetMaterialCommand';
+import SetMaterialColorCommand from '../command/SetMaterialColorCommand';
+import SetMaterialValueCommand from '../command/SetMaterialValueCommand';
+import SetMaterialMapCommand from '../command/SetMaterialMapCommand';
+import ShaderMaterialVertex from './shader/shader_material_vertex.glsl';
+import ShaderMaterialFragment from './shader/shader_material_fragment.glsl';
+import RawShaderMaterialVertex from './shader/raw_shader_material_vertex.glsl';
+import RawShaderMaterialFragment from './shader/raw_shader_material_fragment.glsl';
+
+import TextureSelectControl from '../editor/control/TextureSelectControl';
+import TextureSettingWindow from '../editor/window/TextureSettingWindow';
+
+/**
+ * 材质组件
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function MaterialComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+MaterialComponent.prototype = Object.create(BaseComponent.prototype);
+MaterialComponent.prototype.constructor = MaterialComponent;
+
+MaterialComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'materialPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '材质组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'select',
+ id: 'type',
+ scope: this.id,
+ options: {
+ 'LineBasicMaterial': '线条材质',
+ 'LineDashedMaterial': '虚线材质',
+ 'MeshBasicMaterial': '基本材质',
+ 'MeshDepthMaterial': '深度材质',
+ 'MeshNormalMaterial': '法向量材质',
+ 'MeshLambertMaterial': '兰伯特材质',
+ 'MeshPhongMaterial': '冯氏材质',
+ 'PointCloudMaterial': '点云材质',
+ 'MeshStandardMaterial': '标准材质',
+ 'MeshPhysicalMaterial': '物理材质',
+ 'SpriteMaterial': '精灵材质',
+ 'ShaderMaterial': '着色器材质',
+ 'RawShaderMaterial': '原始着色器材质'
+ },
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'programRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '着色程序'
+ }, {
+ xtype: 'button',
+ scope: this.id,
+ text: '信息',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: this.editProgramInfo.bind(this)
+ }, {
+ xtype: 'button',
+ scope: this.id,
+ text: '顶点',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: this.editVertexShader.bind(this)
+ }, {
+ xtype: 'button',
+ scope: this.id,
+ text: '片源',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: this.editFragmentShader.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'colorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '颜色'
+ }, {
+ xtype: 'color',
+ id: 'color',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'roughnessRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '粗糙度'
+ }, {
+ xtype: 'number',
+ id: 'roughness',
+ scope: this.id,
+ value: 0.5,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'metalnessRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '金属度'
+ }, {
+ xtype: 'number',
+ id: 'metalness',
+ scope: this.id,
+ value: 0.5,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'emissiveRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '发光'
+ }, {
+ xtype: 'color',
+ id: 'emissive',
+ scope: this.id,
+ value: 0x000000,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'specularRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '镜面度'
+ }, {
+ xtype: 'color',
+ id: 'specular',
+ scope: this.id,
+ value: 0x111111,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'shininessRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '光亮度'
+ }, {
+ xtype: 'number',
+ id: 'shininess',
+ scope: this.id,
+ value: 30,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'clearCoatRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '透明度'
+ }, {
+ xtype: 'number',
+ id: 'clearCoat',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'clearCoatRoughnessRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '透明粗糙度'
+ }, {
+ xtype: 'number',
+ id: 'clearCoatRoughness',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'vertexColorsRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '顶点颜色'
+ }, {
+ xtype: 'select',
+ id: 'vertexColors',
+ scope: this.id,
+ options: {
+ 0: '无',
+ 1: '面',
+ 2: '顶点'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'skinningRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '皮肤'
+ }, {
+ xtype: 'checkbox',
+ id: 'skinning',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'mapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'mapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ },
+ new TextureSelectControl({
+ app: this.app,
+ id: 'map',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }), {
+ xtype: 'linkbutton',
+ text: '设置',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: this.onSetMap.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'alphaMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '透明纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'alphaMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'alphaMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'bumpMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '凹凸纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'bumpMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'bumpMap',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '30px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'bumpScale',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '30px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'normalMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '法线纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'normalMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'normalMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'displacementMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '位移纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'displacementMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'displacementMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'displacementScale',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '30px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'roughnessMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '粗糙纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'roughnessMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'roughnessMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'metalnessMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '金属纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'metalnessMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'metalnessMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'specularMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '镜面纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'specularMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'specularMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'envMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '环境纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'envMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'envMap',
+ scope: this.id,
+ mapping: THREE.SphericalReflectionMapping,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'reflectivity',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '30px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'lightMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '光照纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'lightMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'lightMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'aoMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '遮挡纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'aoMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'aoMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'aoScale',
+ scope: this.id,
+ value: 1,
+ range: [0, 1],
+ style: {
+ width: '30px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'emissiveMapRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '放射纹理'
+ }, {
+ xtype: 'checkbox',
+ id: 'emissiveMapEnabled',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'texture',
+ id: 'emissiveMap',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'sideRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '剔除'
+ }, {
+ xtype: 'select',
+ id: 'side',
+ scope: this.id,
+ options: {
+ 0: '正面',
+ 1: '反面',
+ 2: '双面'
+ },
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'flatShadingRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '平滑'
+ }, {
+ xtype: 'checkbox',
+ id: 'flatShading',
+ scope: this.id,
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'blendingRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '混合'
+ }, {
+ xtype: 'select',
+ id: 'blending',
+ scope: this.id,
+ options: {
+ 0: '不混合',
+ 1: '一般混合',
+ 2: '和混合',
+ 3: '差混合',
+ 4: '积混合',
+ 5: '自定义混合'
+ },
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'opacityRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '不透明度'
+ }, {
+ xtype: 'number',
+ id: 'opacity',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'transparentRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '透明'
+ }, {
+ xtype: 'checkbox',
+ id: 'transparent',
+ scope: this.id,
+ style: {
+ left: '100px'
+ },
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'alphaTestRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'α测试'
+ }, {
+ xtype: 'number',
+ id: 'alphaTest',
+ scope: this.id,
+ style: {
+ width: '60px'
+ },
+ range: [0, 1],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'wireframeRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '线框'
+ }, {
+ xtype: 'checkbox',
+ id: 'wireframe',
+ scope: this.id,
+ value: false,
+ onChange: this.updateMaterial.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'wireframeLinewidth',
+ scope: this.id,
+ value: 1,
+ style: {
+ width: '60px'
+ },
+ range: [0, 100],
+ onChange: this.updateMaterial.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+MaterialComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+MaterialComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+MaterialComponent.prototype.updateUI = function () {
+ var container = UI.get('materialPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && (editor.selected instanceof THREE.Mesh || editor.selected instanceof THREE.Sprite) && !Array.isArray(editor.selected.material)) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ this.setRowVisibility();
+ this.setRowValue();
+};
+
+MaterialComponent.prototype.setRowVisibility = function () {
+ var programRow = UI.get('programRow', this.id);
+ var colorRow = UI.get('colorRow', this.id);
+ var roughnessRow = UI.get('roughnessRow', this.id);
+ var metalnessRow = UI.get('metalnessRow', this.id);
+ var emissiveRow = UI.get('emissiveRow', this.id);
+ var specularRow = UI.get('specularRow', this.id);
+ var shininessRow = UI.get('shininessRow', this.id);
+ var clearCoatRow = UI.get('clearCoatRow', this.id);
+ var clearCoatRoughnessRow = UI.get('clearCoatRoughnessRow', this.id);
+ var vertexColorsRow = UI.get('vertexColorsRow', this.id);
+ var skinningRow = UI.get('skinningRow', this.id);
+ var mapRow = UI.get('mapRow', this.id);
+ var alphaMapRow = UI.get('alphaMapRow', this.id);
+ var bumpMapRow = UI.get('bumpMapRow', this.id);
+ var normalMapRow = UI.get('normalMapRow', this.id);
+ var displacementMapRow = UI.get('displacementMapRow', this.id);
+ var roughnessMapRow = UI.get('roughnessMapRow', this.id);
+ var metalnessMapRow = UI.get('metalnessMapRow', this.id);
+ var specularMapRow = UI.get('specularMapRow', this.id);
+ var envMapRow = UI.get('envMapRow', this.id);
+ var lightMapRow = UI.get('lightMapRow', this.id);
+ var aoMapRow = UI.get('aoMapRow', this.id);
+ var emissiveMapRow = UI.get('emissiveMapRow', this.id);
+ var sideRow = UI.get('sideRow', this.id);
+ var flatShadingRow = UI.get('flatShadingRow', this.id);
+ var blendingRow = UI.get('blendingRow', this.id);
+ var opacityRow = UI.get('opacityRow', this.id);
+ var transparentRow = UI.get('transparentRow', this.id);
+ var alphaTestRow = UI.get('alphaTestRow', this.id);
+ var wireframeRow = UI.get('wireframeRow', this.id);
+
+ var properties = {
+ 'vertexShader': programRow,
+ 'color': colorRow,
+ 'roughness': roughnessRow,
+ 'metalness': metalnessRow,
+ 'emissive': emissiveRow,
+ 'specular': specularRow,
+ 'shininess': shininessRow,
+ 'clearCoat': clearCoatRow,
+ 'clearCoatRoughness': clearCoatRoughnessRow,
+ 'vertexColors': vertexColorsRow,
+ 'skinning': skinningRow,
+ 'map': mapRow,
+ 'alphaMap': alphaMapRow,
+ 'bumpMap': bumpMapRow,
+ 'normalMap': normalMapRow,
+ 'displacementMap': displacementMapRow,
+ 'roughnessMap': roughnessMapRow,
+ 'metalnessMap': metalnessMapRow,
+ 'specularMap': specularMapRow,
+ 'envMap': envMapRow,
+ 'lightMap': lightMapRow,
+ 'aoMap': aoMapRow,
+ 'emissiveMap': emissiveMapRow,
+ 'side': sideRow,
+ 'flatShading': flatShadingRow,
+ 'blending': blendingRow,
+ 'opacity': opacityRow,
+ 'transparent': transparentRow,
+ 'alphaTest': alphaTestRow,
+ 'wireframe': wireframeRow
+ };
+
+ var material = this.selected.material;
+ for (var property in properties) {
+ properties[property].dom.style.display = material[property] === undefined ? 'none' : ''
+ }
+};
+
+MaterialComponent.prototype.setRowValue = function () {
+ var type = UI.get('type', this.id);
+ var color = UI.get('color', this.id);
+ var roughness = UI.get('roughness', this.id);
+ var metalness = UI.get('metalness', this.id);
+ var emissive = UI.get('emissive', this.id);
+ var specular = UI.get('specular', this.id);
+ var shininess = UI.get('shininess', this.id);
+ var clearCoat = UI.get('clearCoat', this.id);
+ var clearCoatRoughness = UI.get('clearCoatRoughness', this.id);
+ var vertexColors = UI.get('vertexColors', this.id);
+ var skinning = UI.get('skinning', this.id);
+ var mapEnabled = UI.get('mapEnabled', this.id);
+ var map = UI.get('map', this.id);
+ var alphaMapEnabled = UI.get('alphaMapEnabled', this.id);
+ var alphaMap = UI.get('alphaMap', this.id);
+ var bumpMapEnabled = UI.get('bumpMapEnabled', this.id);
+ var bumpMap = UI.get('bumpMap', this.id);
+ var bumpScale = UI.get('bumpScale', this.id);
+ var normalMapEnabled = UI.get('normalMapEnabled', this.id);
+ var normalMap = UI.get('normalMap', this.id);
+ var displacementMapEnabled = UI.get('displacementMapEnabled', this.id);
+ var displacementMap = UI.get('displacementMap', this.id);
+ var displacementScale = UI.get('displacementScale', this.id);
+ var roughnessMapEnabled = UI.get('roughnessMapEnabled', this.id);
+ var roughnessMap = UI.get('roughnessMap', this.id);
+ var metalnessMapEnabled = UI.get('metalnessMapEnabled', this.id);
+ var metalnessMap = UI.get('metalnessMap', this.id);
+ var specularMapEnabled = UI.get('specularMapEnabled', this.id);
+ var specularMap = UI.get('specularMap', this.id);
+ var envMapEnabled = UI.get('envMapEnabled', this.id);
+ var envMap = UI.get('envMap', this.id);
+ var reflectivity = UI.get('reflectivity', this.id);
+ var lightMapEnabled = UI.get('lightMapEnabled', this.id);
+ var lightMap = UI.get('lightMap', this.id);
+ var aoMapEnabled = UI.get('aoMapEnabled', this.id);
+ var aoMap = UI.get('aoMap', this.id);
+ var aoScale = UI.get('aoScale', this.id);
+ var emissiveMapEnabled = UI.get('emissiveMapEnabled', this.id);
+ var emissiveMap = UI.get('emissiveMap', this.id);
+ var side = UI.get('side', this.id);
+ var flatShading = UI.get('flatShading', this.id);
+ var blending = UI.get('blending', this.id);
+ var opacity = UI.get('opacity', this.id);
+ var transparent = UI.get('transparent', this.id);
+ var alphaTest = UI.get('alphaTest', this.id);
+ var wireframe = UI.get('wireframe', this.id);
+ var wireframeLinewidth = UI.get('wireframeLinewidth', this.id);
+
+ var material = this.selected.material;
+
+ type.setValue(material.type);
+
+ if (material.color !== undefined) {
+ color.setHexValue(material.color.getHexString());
+ }
+
+ if (material.roughness !== undefined) {
+ roughness.setValue(material.roughness);
+ }
+
+ if (material.metalness !== undefined) {
+ metalness.setValue(material.metalness);
+ }
+
+ if (material.emissive !== undefined) {
+ emissive.setHexValue(material.emissive.getHexString());
+ }
+
+ if (material.specular !== undefined) {
+ specular.setHexValue(material.specular.getHexString());
+ }
+
+ if (material.shininess !== undefined) {
+ shininess.setValue(material.shininess);
+ }
+
+ if (material.clearCoat !== undefined) {
+ clearCoat.setValue(material.clearCoat);
+ }
+
+ if (material.clearCoatRoughness !== undefined) {
+ clearCoatRoughness.setValue(material.clearCoatRoughness);
+ }
+
+ if (material.vertexColors !== undefined) {
+ vertexColors.setValue(material.vertexColors);
+ }
+
+ if (material.skinning !== undefined) {
+ skinning.setValue(material.skinning);
+ }
+
+ if (material.map) {
+ mapEnabled.setValue(material.map !== null);
+
+ if (material.map !== null) {
+ map.setValue(material.map);
+ }
+ }
+
+ if (material.alphaMap !== undefined) {
+ alphaMapEnabled.setValue(material.alphaMap !== null);
+
+ if (material.alphaMap !== null) {
+ alphaMap.setValue(material.alphaMap);
+ }
+ }
+
+ if (material.bumpMap !== undefined) {
+ bumpMapEnabled.setValue(material.bumpMap !== null);
+
+ if (material.bumpMap !== null) {
+ bumpMap.setValue(material.bumpMap);
+ bumpScale.setValue(material.bumpScale);
+ }
+ }
+
+ if (material.normalMap !== undefined) {
+ normalMapEnabled.setValue(material.normalMap !== null);
+
+ if (material.normalMap !== null) {
+ normalMap.setValue(material.normalMap);
+ }
+ }
+
+ if (material.displacementMap !== undefined) {
+ displacementMapEnabled.setValue(material.displacementMap !== null);
+
+ if (material.displacementMap !== null) {
+ displacementMap.setValue(material.displacementMap);
+ displacementScale.setValue(material.displacementScale);
+ }
+ }
+
+ if (material.roughnessMap !== undefined) {
+ roughnessMapEnabled.setValue(material.roughnessMap !== null);
+
+ if (material.roughnessMap !== null) {
+ roughnessMap.setValue(material.roughnessMap);
+ }
+ }
+
+ if (material.metalnessMap !== undefined) {
+ metalnessMapEnabled.setValue(material.metalnessMap !== null);
+
+ if (material.metalnessMap !== null) {
+ metalnessMap.setValue(material.metalnessMap);
+ }
+ }
+
+ if (material.specularMap !== undefined) {
+ specularMapEnabled.setValue(material.specularMap !== null);
+
+ if (material.specularMap !== null) {
+ specularMap.setValue(material.specularMap);
+ }
+ }
+
+ if (material.envMap !== undefined) {
+ envMapEnabled.setValue(material.envMap !== null);
+
+ if (material.envMap !== null) {
+ envMap.setValue(material.envMap);
+ }
+ }
+
+ if (material.reflectivity !== undefined) {
+ reflectivity.setValue(material.reflectivity);
+ }
+
+ if (material.lightMap !== undefined) {
+ lightMapEnabled.setValue(material.lightMap !== null);
+
+ if (material.lightMap !== null) {
+ lightMap.setValue(material.lightMap);
+ }
+ }
+
+ if (material.aoMap !== undefined) {
+ aoMapEnabled.setValue(material.aoMap !== null);
+
+ if (material.aoMap !== null) {
+ aoMap.setValue(material.aoMap);
+ aoScale.setValue(material.aoMapIntensity);
+ }
+ }
+
+ if (material.emissiveMap !== undefined) {
+ emissiveMapEnabled.setValue(material.emissiveMap !== null);
+
+ if (material.emissiveMap !== null) {
+ emissiveMap.setValue(material.emissiveMap);
+ }
+ }
+
+ if (material.side !== undefined) {
+ side.setValue(material.side);
+ }
+
+ if (material.flatShading !== undefined) {
+ flatShading.setValue(material.flatShading);
+ }
+
+ if (material.blending !== undefined) {
+ blending.setValue(material.blending);
+ }
+
+ if (material.opacity !== undefined) {
+ opacity.setValue(material.opacity);
+ }
+
+ if (material.transparent !== undefined) {
+ transparent.setValue(material.transparent);
+ }
+
+ if (material.alphaTest !== undefined) {
+ alphaTest.setValue(material.alphaTest);
+ }
+
+ if (material.wireframe !== undefined) {
+ wireframe.setValue(material.wireframe);
+ }
+
+ if (material.wireframeLinewidth !== undefined) {
+ wireframeLinewidth.setValue(material.wireframeLinewidth);
+ }
+};
+
+MaterialComponent.prototype.updateMaterial = function () {
+ var type = UI.get('type', this.id);
+ var color = UI.get('color', this.id);
+ var roughness = UI.get('roughness', this.id);
+ var metalness = UI.get('metalness', this.id);
+ var emissive = UI.get('emissive', this.id);
+ var specular = UI.get('specular', this.id);
+ var shininess = UI.get('shininess', this.id);
+ var clearCoat = UI.get('clearCoat', this.id);
+ var clearCoatRoughness = UI.get('clearCoatRoughness', this.id);
+ var vertexColors = UI.get('vertexColors', this.id);
+ var skinning = UI.get('skinning', this.id);
+ var mapEnabled = UI.get('mapEnabled', this.id);
+ var map = UI.get('map', this.id);
+ var alphaMapEnabled = UI.get('alphaMapEnabled', this.id);
+ var alphaMap = UI.get('alphaMap', this.id);
+ var bumpMapEnabled = UI.get('bumpMapEnabled', this.id);
+ var bumpMap = UI.get('bumpMap', this.id);
+ var bumpScale = UI.get('bumpScale', this.id);
+ var normalMapEnabled = UI.get('normalMapEnabled', this.id);
+ var normalMap = UI.get('normalMap', this.id);
+ var displacementMapEnabled = UI.get('displacementMapEnabled', this.id);
+ var displacementMap = UI.get('displacementMap', this.id);
+ var displacementScale = UI.get('displacementScale', this.id);
+ var roughnessMapEnabled = UI.get('roughnessMapEnabled', this.id);
+ var roughnessMap = UI.get('roughnessMap', this.id);
+ var metalnessMapEnabled = UI.get('metalnessMapEnabled', this.id);
+ var metalnessMap = UI.get('metalnessMap', this.id);
+ var specularMapEnabled = UI.get('specularMapEnabled', this.id);
+ var specularMap = UI.get('specularMap', this.id);
+ var envMapEnabled = UI.get('envMapEnabled', this.id);
+ var envMap = UI.get('envMap', this.id);
+ var reflectivity = UI.get('reflectivity', this.id);
+ var lightMapEnabled = UI.get('lightMapEnabled', this.id);
+ var lightMap = UI.get('lightMap', this.id);
+ var aoMapEnabled = UI.get('aoMapEnabled', this.id);
+ var aoMap = UI.get('aoMap', this.id);
+ var aoScale = UI.get('aoScale', this.id);
+ var emissiveMapEnabled = UI.get('emissiveMapEnabled', this.id);
+ var emissiveMap = UI.get('emissiveMap', this.id);
+ var side = UI.get('side', this.id);
+ var flatShading = UI.get('flatShading', this.id);
+ var blending = UI.get('blending', this.id);
+ var opacity = UI.get('opacity', this.id);
+ var transparent = UI.get('transparent', this.id);
+ var alphaTest = UI.get('alphaTest', this.id);
+ var wireframe = UI.get('wireframe', this.id);
+ var wireframeLinewidth = UI.get('wireframeLinewidth', this.id);
+
+ var editor = this.app.editor;
+ var object = this.selected;
+ var geometry = object.geometry;
+ var material = object.material;
+
+ var textureWarning = false;
+ var objectHasUvs = false;
+
+ if (object instanceof THREE.Sprite) {
+ objectHasUvs = true;
+ }
+
+ if (geometry instanceof THREE.Geometry && geometry.faceVertexUvs[0].length > 0) {
+ objectHasUvs = true;
+ }
+
+ if (geometry instanceof THREE.BufferGeometry && geometry.attributes.uv !== undefined) {
+ objectHasUvs = true;
+ }
+
+ if (material instanceof THREE[type.getValue()] === false) {
+ material = new THREE[type.getValue()]();
+
+ if (material instanceof THREE.ShaderMaterial) {
+ material.uniforms = {
+ time: {
+ value: 1.0
+ }
+ };
+ material.vertexShader = ShaderMaterialVertex;
+ material.fragmentShader = ShaderMaterialFragment;
+ }
+
+ if (material instanceof THREE.RawShaderMaterial) {
+ material.uniforms = {
+ time: {
+ value: 1.0
+ }
+ };
+ material.vertexShader = RawShaderMaterialVertex;
+ material.fragmentShader = RawShaderMaterialFragment;
+ }
+
+ editor.execute(new SetMaterialCommand(object, material), '新材质:' + type.getValue());
+ }
+
+ if (material.color !== undefined && material.color.getHex() !== color.getHexValue()) {
+ editor.execute(new SetMaterialColorCommand(object, 'color', color.getHexValue()));
+ }
+
+ if (material.roughness !== undefined && Math.abs(material.roughness - roughness.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'roughness', roughness.getValue()));
+ }
+
+ if (material.metalness !== undefined && Math.abs(material.metalness - metalness.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'metalness', metalness.getValue()));
+ }
+
+ if (material.emissive !== undefined && material.emissive.getHex() !== emissive.getHexValue()) {
+ editor.execute(new SetMaterialColorCommand(object, 'emissive', emissive.getHexValue()));
+ }
+
+ if (material.specular !== undefined && material.specular.getHex() !== specular.getHexValue()) {
+ editor.execute(new SetMaterialColorCommand(object, 'specular', specular.getHexValue()));
+ }
+
+ if (material.shininess !== undefined && Math.abs(material.shininess - shininess.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'shininess', shininess.getValue()));
+ }
+
+ if (material.clearCoat !== undefined && Math.abs(material.clearCoat - clearCoat.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'clearCoat', clearCoat.getValue()));
+ }
+
+ if (material.clearCoatRoughness !== undefined && Math.abs(material.clearCoatRoughness - clearCoatRoughness.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'clearCoatRoughness', clearCoatRoughness.getValue()));
+ }
+
+ if (material.vertexColors !== undefined) {
+ if (material.vertexColors !== parseInt(vertexColors.getValue())) {
+ editor.execute(new SetMaterialValueCommand(object, 'vertexColors', parseInt(vertexColors.getValue())));
+ }
+ }
+
+ if (material.skinning !== undefined && material.skinning !== skinning.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'skinning', skinning.getValue()));
+ }
+
+ if (material.map !== undefined) {
+ var mapEnabled = mapEnabled.getValue() === true;
+ if (objectHasUvs) {
+ var map = mapEnabled ? map.getValue() : null;
+ if (material.map !== map) {
+ editor.execute(new SetMaterialMapCommand(object, 'map', map));
+ }
+ } else {
+ if (mapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.alphaMap !== undefined) {
+ var mapEnabled = alphaMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var alphaMap = mapEnabled ? alphaMap.getValue() : null;
+
+ if (material.alphaMap !== alphaMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'alphaMap', alphaMap));
+ }
+ } else {
+ if (mapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.bumpMap !== undefined) {
+ var bumpMapEnabled = bumpMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var bumpMap = bumpMapEnabled ? bumpMap.getValue() : null;
+
+ if (material.bumpMap !== bumpMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'bumpMap', bumpMap));
+ }
+
+ if (material.bumpScale !== bumpScale.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'bumpScale', bumpScale.getValue()));
+ }
+ } else {
+ if (bumpMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.normalMap !== undefined) {
+ var normalMapEnabled = normalMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var normalMap = normalMapEnabled ? normalMap.getValue() : null;
+
+ if (material.normalMap !== normalMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'normalMap', normalMap));
+ }
+ } else {
+ if (normalMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.displacementMap !== undefined) {
+ var displacementMapEnabled = displacementMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var displacementMap = displacementMapEnabled ? displacementMap.getValue() : null;
+
+ if (material.displacementMap !== displacementMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'displacementMap', displacementMap));
+ }
+
+ if (material.displacementScale !== displacementScale.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'displacementScale', displacementScale.getValue()));
+ }
+ } else {
+ if (displacementMapEnabled) textureWarning = true;
+ }
+
+ }
+
+ if (material.roughnessMap !== undefined) {
+ var roughnessMapEnabled = roughnessMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var roughnessMap = roughnessMapEnabled ? roughnessMap.getValue() : null;
+
+ if (material.roughnessMap !== roughnessMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'roughnessMap', roughnessMap));
+ }
+ } else {
+ if (roughnessMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.metalnessMap !== undefined) {
+ var metalnessMapEnabled = metalnessMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var metalnessMap = metalnessMapEnabled ? metalnessMap.getValue() : null;
+
+ if (material.metalnessMap !== metalnessMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'metalnessMap', metalnessMap));
+ }
+ } else {
+ if (metalnessMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.specularMap !== undefined) {
+ var specularMapEnabled = specularMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var specularMap = specularMapEnabled ? specularMap.getValue() : null;
+
+ if (material.specularMap !== specularMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'specularMap', specularMap));
+ }
+ } else {
+ if (specularMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.envMap !== undefined) {
+ var envMapEnabled = envMapEnabled.getValue() === true;
+ var envMap = envMapEnabled ? envMap.getValue() : null;
+
+ if (material.envMap !== envMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'envMap', envMap));
+ }
+ }
+
+ if (material.reflectivity !== undefined) {
+ var reflectivity = reflectivity.getValue();
+
+ if (material.reflectivity !== reflectivity) {
+ editor.execute(new SetMaterialValueCommand(object, 'reflectivity', reflectivity));
+ }
+ }
+
+ if (material.lightMap !== undefined) {
+ var lightMapEnabled = lightMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var lightMap = lightMapEnabled ? lightMap.getValue() : null;
+
+ if (material.lightMap !== lightMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'lightMap', lightMap));
+ }
+ } else {
+ if (lightMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.aoMap !== undefined) {
+ var aoMapEnabled = aoMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var aoMap = aoMapEnabled ? aoMap.getValue() : null;
+
+ if (material.aoMap !== aoMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'aoMap', aoMap));
+ }
+
+ if (material.aoMapIntensity !== aoScale.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'aoMapIntensity', aoScale.getValue()));
+ }
+ } else {
+ if (aoMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.emissiveMap !== undefined) {
+ var emissiveMapEnabled = emissiveMapEnabled.getValue() === true;
+
+ if (objectHasUvs) {
+ var emissiveMap = emissiveMapEnabled ? emissiveMap.getValue() : null;
+
+ if (material.emissiveMap !== emissiveMap) {
+ editor.execute(new SetMaterialMapCommand(object, 'emissiveMap', emissiveMap));
+ }
+ } else {
+ if (emissiveMapEnabled) textureWarning = true;
+ }
+ }
+
+ if (material.side !== undefined) {
+ var side = parseInt(side.getValue());
+
+ if (material.side !== side) {
+ editor.execute(new SetMaterialValueCommand(object, 'side', side));
+ }
+ }
+
+ if (material.flatShading !== undefined) {
+ var flatShading = flatShading.getValue();
+
+ if (material.flatShading != flatShading) {
+ editor.execute(new SetMaterialValueCommand(object, 'flatShading', flatShading));
+ }
+ }
+
+ if (material.blending !== undefined) {
+ var blending = parseInt(blending.getValue());
+
+ if (material.blending !== blending) {
+ editor.execute(new SetMaterialValueCommand(object, 'blending', blending));
+ }
+ }
+
+ if (material.opacity !== undefined && Math.abs(material.opacity - opacity.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'opacity', opacity.getValue()));
+ }
+
+ if (material.transparent !== undefined && material.transparent !== transparent.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'transparent', transparent.getValue()));
+ }
+
+ if (material.alphaTest !== undefined && Math.abs(material.alphaTest - alphaTest.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'alphaTest', alphaTest.getValue()));
+ }
+
+ if (material.wireframe !== undefined && material.wireframe !== wireframe.getValue()) {
+ editor.execute(new SetMaterialValueCommand(object, 'wireframe', wireframe.getValue()));
+ }
+
+ if (material.wireframeLinewidth !== undefined && Math.abs(material.wireframeLinewidth - wireframeLinewidth.getValue()) >= 0.01) {
+ editor.execute(new SetMaterialValueCommand(object, 'wireframeLinewidth', wireframeLinewidth.getValue()));
+ }
+
+ this.updateUI();
+
+ if (textureWarning) {
+ console.warn(`无法设置纹理,${this.selected.name}的材质没有纹理坐标!`);
+ }
+};
+
+MaterialComponent.prototype.editProgramInfo = function () {
+ var material = this.selected.material;
+
+ var obj = {
+ defines: material.defines,
+ uniforms: material.uniforms,
+ attributes: material.attributes
+ };
+
+ this.app.script.open(material.uuid, this.selected.name + '-ProgramInfo', 'json', JSON.stringify(obj), this.selected.name + '-着色器信息', source => {
+ try {
+ obj = JSON.parse(source);
+ material.defines = obj.defines;
+ material.uniforms = obj.uniforms;
+ material.attributes = obj.attributes;
+ material.needsUpdate = true;
+ } catch (e) {
+ this.app.error(this.selected.name + '-着色器信息 无法反序列化。');
+ }
+ });
+};
+
+MaterialComponent.prototype.editVertexShader = function () {
+ var material = this.selected.material;
+
+ this.app.script.open(material.uuid, this.selected.name + '-VertexShader', 'vertexShader', material.vertexShader, this.selected.name + '-顶点着色器', source => {
+ material.vertexShader = source;
+ material.needsUpdate = true;
+ });
+};
+
+MaterialComponent.prototype.editFragmentShader = function () {
+ var material = this.selected.material;
+
+ this.app.script.open(material.uuid, this.selected.name + '-FragmentShader', 'fragmentShader', material.fragmentShader, this.selected.name + '-片源着色器', source => {
+ material.fragmentShader = source;
+ material.needsUpdate = true;
+ });
+};
+
+MaterialComponent.prototype.onSetMap = function () {
+ if (this.mapSettingWindow === undefined) {
+ this.mapSettingWindow = new TextureSettingWindow({
+ app: this.app
+ });
+ this.mapSettingWindow.render();
+ }
+
+ if (!this.selected.material.map) {
+ UI.msg('请先为该物体选择纹理!');
+ return;
+ }
+
+ this.mapSettingWindow.setData(this.selected.material.map);
+ this.mapSettingWindow.show();
+};
+
+export default MaterialComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/ParticleEmitterComponent.js b/ShadowEditor.Core/src/component/ParticleEmitterComponent.js
new file mode 100644
index 00000000..d824b104
--- /dev/null
+++ b/ShadowEditor.Core/src/component/ParticleEmitterComponent.js
@@ -0,0 +1,583 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 粒子发射器组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ParticleEmitterComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+
+ this.isPlaying = false;
+}
+
+ParticleEmitterComponent.prototype = Object.create(BaseComponent.prototype);
+ParticleEmitterComponent.prototype.constructor = ParticleEmitterComponent;
+
+ParticleEmitterComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'particleEmitterPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '粒子发射器'
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '位置'
+ }, {
+ xtype: 'number',
+ id: 'positionX',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'positionY',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'positionZ',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '位置发散'
+ }, {
+ xtype: 'number',
+ id: 'positionSpreadX',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'positionSpreadY',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'positionSpreadZ',
+ scope: this.id,
+ onChange: this.onChangePosition.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '速度'
+ }, {
+ xtype: 'number',
+ id: 'velocityX',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'velocityY',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'velocityZ',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '速度发散'
+ }, {
+ xtype: 'number',
+ id: 'velocitySpreadX',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'velocitySpreadY',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'velocitySpreadZ',
+ scope: this.id,
+ onChange: this.onChangeVelocity.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '加速度'
+ }, {
+ xtype: 'number',
+ id: 'accelerationX',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'accelerationY',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'accelerationZ',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '加速度发散'
+ }, {
+ xtype: 'number',
+ id: 'accelerationSpreadX',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'accelerationSpreadY',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'accelerationSpreadZ',
+ scope: this.id,
+ onChange: this.onChangeAcceleration.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '颜色1'
+ }, {
+ xtype: 'color',
+ id: 'color1',
+ scope: this.id,
+ onChange: this.onChangeColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '颜色2'
+ }, {
+ xtype: 'color',
+ id: 'color2',
+ scope: this.id,
+ onChange: this.onChangeColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '颜色3'
+ }, {
+ xtype: 'color',
+ id: 'color3',
+ scope: this.id,
+ onChange: this.onChangeColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '颜色4'
+ }, {
+ xtype: 'color',
+ id: 'color4',
+ scope: this.id,
+ onChange: this.onChangeColor.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '尺寸'
+ }, {
+ xtype: 'number',
+ id: 'size',
+ scope: this.id,
+ onChange: this.onChangeSize.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '尺寸发散'
+ }, {
+ xtype: 'number',
+ id: 'sizeSpread',
+ scope: this.id,
+ onChange: this.onChangeSize.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '纹理'
+ }, {
+ xtype: 'texture',
+ id: 'texture',
+ scope: this.id,
+ onChange: this.onChangeTexture.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '粒子数量'
+ }, {
+ xtype: 'int',
+ range: [1, Infinity],
+ id: 'particleCount',
+ scope: this.id,
+ onChange: this.onChangeParticleCount.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '持续时长'
+ }, {
+ xtype: 'number',
+ id: 'maxAge',
+ scope: this.id,
+ onChange: this.onChangeMaxAge.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '持续时长发散'
+ }, {
+ xtype: 'number',
+ id: 'maxAgeSpread',
+ scope: this.id,
+ onChange: this.onChangeMaxAgeSpread.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label'
+ }, {
+ xtype: 'button',
+ id: 'btnPreview',
+ scope: this.id,
+ text: '预览',
+ onClick: this.onPreview.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+ParticleEmitterComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+ParticleEmitterComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+ParticleEmitterComponent.prototype.updateUI = function () {
+ var container = UI.get('particleEmitterPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected.userData.type === 'ParticleEmitter') {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var positionX = UI.get('positionX', this.id);
+ var positionY = UI.get('positionY', this.id);
+ var positionZ = UI.get('positionZ', this.id);
+
+ var positionSpreadX = UI.get('positionSpreadX', this.id);
+ var positionSpreadY = UI.get('positionSpreadY', this.id);
+ var positionSpreadZ = UI.get('positionSpreadZ', this.id);
+
+ var velocityX = UI.get('velocityX', this.id);
+ var velocityY = UI.get('velocityY', this.id);
+ var velocityZ = UI.get('velocityZ', this.id);
+
+ var velocitySpreadX = UI.get('velocitySpreadX', this.id);
+ var velocitySpreadY = UI.get('velocitySpreadY', this.id);
+ var velocitySpreadZ = UI.get('velocitySpreadZ', this.id);
+
+ var accelerationX = UI.get('accelerationX', this.id);
+ var accelerationY = UI.get('accelerationY', this.id);
+ var accelerationZ = UI.get('accelerationZ', this.id);
+
+ var accelerationSpreadX = UI.get('accelerationSpreadX', this.id);
+ var accelerationSpreadY = UI.get('accelerationSpreadY', this.id);
+ var accelerationSpreadZ = UI.get('accelerationSpreadZ', this.id);
+
+ var color1 = UI.get('color1', this.id);
+ var color2 = UI.get('color2', this.id);
+ var color3 = UI.get('color3', this.id);
+ var color4 = UI.get('color4', this.id);
+
+ var size = UI.get('size', this.id);
+ var sizeSpread = UI.get('sizeSpread', this.id);
+ var texture = UI.get('texture', this.id);
+ var particleCount = UI.get('particleCount', this.id);
+ var maxAge = UI.get('maxAge', this.id);
+ var maxAgeSpread = UI.get('maxAgeSpread', this.id);
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ positionX.setValue(emitter.position.value.x);
+ positionY.setValue(emitter.position.value.y);
+ positionZ.setValue(emitter.position.value.z);
+
+ positionSpreadX.setValue(emitter.position.spread.x);
+ positionSpreadY.setValue(emitter.position.spread.y);
+ positionSpreadZ.setValue(emitter.position.spread.z);
+
+ velocityX.setValue(emitter.velocity.value.x);
+ velocityY.setValue(emitter.velocity.value.y);
+ velocityZ.setValue(emitter.velocity.value.z);
+
+ velocitySpreadX.setValue(emitter.velocity.spread.x);
+ velocitySpreadY.setValue(emitter.velocity.spread.y);
+ velocitySpreadZ.setValue(emitter.velocity.spread.z);
+
+ accelerationX.setValue(emitter.acceleration.value.x);
+ accelerationY.setValue(emitter.acceleration.value.y);
+ accelerationZ.setValue(emitter.acceleration.value.z);
+
+ accelerationSpreadX.setValue(emitter.acceleration.spread.x);
+ accelerationSpreadY.setValue(emitter.acceleration.spread.y);
+ accelerationSpreadZ.setValue(emitter.acceleration.spread.z);
+
+ color1.setValue(`#${emitter.color.value[0].getHexString()}`);
+ color2.setValue(`#${emitter.color.value[1].getHexString()}`);
+ color3.setValue(`#${emitter.color.value[2].getHexString()}`);
+ color4.setValue(`#${emitter.color.value[3].getHexString()}`);
+
+ size.setValue(emitter.size.value[0]);
+ sizeSpread.setValue(emitter.size.spread[0]);
+ texture.setValue(group.texture);
+ particleCount.setValue(emitter.particleCount);
+ maxAge.setValue(emitter.maxAge.value);
+ maxAgeSpread.setValue(emitter.maxAge.spread);
+
+ if (this.isPlaying) {
+ btnPreview.setText('取消');
+ } else {
+ btnPreview.setText('预览');
+ }
+};
+
+ParticleEmitterComponent.prototype.onChangePosition = function () {
+ var positionX = UI.get('positionX', this.id);
+ var positionY = UI.get('positionY', this.id);
+ var positionZ = UI.get('positionZ', this.id);
+
+ var positionSpreadX = UI.get('positionSpreadX', this.id);
+ var positionSpreadY = UI.get('positionSpreadY', this.id);
+ var positionSpreadZ = UI.get('positionSpreadZ', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.position.value.x = positionX.getValue();
+ emitter.position.value.y = positionY.getValue();
+ emitter.position.value.z = positionZ.getValue();
+
+ emitter.position.spread.x = positionSpreadX.getValue();
+ emitter.position.spread.y = positionSpreadY.getValue();
+ emitter.position.spread.z = positionSpreadZ.getValue();
+
+ emitter.updateFlags.position = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeVelocity = function () {
+ var velocityX = UI.get('velocityX', this.id);
+ var velocityY = UI.get('velocityY', this.id);
+ var velocityZ = UI.get('velocityZ', this.id);
+
+ var velocitySpreadX = UI.get('velocitySpreadX', this.id);
+ var velocitySpreadY = UI.get('velocitySpreadY', this.id);
+ var velocitySpreadZ = UI.get('velocitySpreadZ', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.velocity.value.x = velocityX.getValue();
+ emitter.velocity.value.y = velocityY.getValue();
+ emitter.velocity.value.z = velocityZ.getValue();
+
+ emitter.velocity.spread.x = velocitySpreadX.getValue();
+ emitter.velocity.spread.y = velocitySpreadY.getValue();
+ emitter.velocity.spread.z = velocitySpreadZ.getValue();
+
+ emitter.updateFlags.velocity = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeAcceleration = function () {
+ var accelerationX = UI.get('accelerationX', this.id);
+ var accelerationY = UI.get('accelerationY', this.id);
+ var accelerationZ = UI.get('accelerationZ', this.id);
+
+ var accelerationSpreadX = UI.get('accelerationSpreadX', this.id);
+ var accelerationSpreadY = UI.get('accelerationSpreadY', this.id);
+ var accelerationSpreadZ = UI.get('accelerationSpreadZ', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.acceleration.value.x = accelerationX.getValue();
+ emitter.acceleration.value.y = accelerationY.getValue();
+ emitter.acceleration.value.z = accelerationZ.getValue();
+
+ emitter.acceleration.spread.x = accelerationSpreadX.getValue();
+ emitter.acceleration.spread.y = accelerationSpreadY.getValue();
+ emitter.acceleration.spread.z = accelerationSpreadZ.getValue();
+
+ emitter.updateFlags.acceleration = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeColor = function () {
+ var color1 = UI.get('color1', this.id);
+ var color2 = UI.get('color2', this.id);
+ var color3 = UI.get('color3', this.id);
+ var color4 = UI.get('color4', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.color.value[0] = new THREE.Color(color1.getHexValue());
+ emitter.color.value[1] = new THREE.Color(color2.getHexValue());
+ emitter.color.value[2] = new THREE.Color(color3.getHexValue());
+ emitter.color.value[3] = new THREE.Color(color4.getHexValue());
+
+ emitter.updateFlags.color = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeSize = function () {
+ var size = UI.get('size', this.id);
+ var sizeSpread = UI.get('sizeSpread', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ for (var i = 0; i < emitter.size.value.length; i++) {
+ emitter.size.value[i] = size.getValue();
+ emitter.size.spread[i] = sizeSpread.getValue();
+ }
+
+ emitter.updateFlags.size = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeTexture = function () {
+ var texture = UI.get('texture', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ texture = texture.getValue();
+ texture.needsUpdate = true;
+
+ group.texture = texture;
+ group.material.uniforms.texture.value = texture;
+};
+
+ParticleEmitterComponent.prototype.onChangeParticleCount = function () {
+ var particleCount = UI.get('particleCount', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.particleCount = particleCount.getValue();
+
+ emitter.updateFlags.params = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeMaxAge = function () {
+ var maxAge = UI.get('maxAge', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.maxAge.value = maxAge.getValue();
+
+ emitter.updateFlags.params = true;
+};
+
+ParticleEmitterComponent.prototype.onChangeMaxAgeSpread = function () {
+ var maxAgeSpread = UI.get('maxAgeSpread', this.id);
+
+ var group = this.selected.userData.group;
+ var emitter = group.emitters[0];
+
+ emitter.maxAge.spread = maxAgeSpread.getValue();
+
+ emitter.updateFlags.params = true;
+};
+
+ParticleEmitterComponent.prototype.onPreview = function () {
+ if (this.isPlaying) {
+ this.stopPreview();
+ } else {
+ this.startPreview();
+ }
+};
+
+ParticleEmitterComponent.prototype.startPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = true;
+ btnPreview.setText('取消');
+
+ this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
+};
+
+ParticleEmitterComponent.prototype.stopPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = false;
+ btnPreview.setText('预览');
+
+ var group = this.selected.userData.group;
+ var emitter = this.selected.userData.emitter;
+
+ group.removeEmitter(emitter);
+ group.addEmitter(emitter);
+ group.tick(0);
+
+ this.app.on(`animate.${this.id}`, null);
+};
+
+ParticleEmitterComponent.prototype.onAnimate = function (clock, deltaTime) {
+ var group = this.selected.userData.group;
+ group.tick(deltaTime);
+};
+
+export default ParticleEmitterComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/ReflectorComponent.js b/ShadowEditor.Core/src/component/ReflectorComponent.js
new file mode 100644
index 00000000..18a4b48d
--- /dev/null
+++ b/ShadowEditor.Core/src/component/ReflectorComponent.js
@@ -0,0 +1,258 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+import RemoveObjectCommand from '../command/RemoveObjectCommand';
+import AddObjectCommand from '../command/AddObjectCommand';
+
+/**
+ * 反光组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ReflectorComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+ReflectorComponent.prototype = Object.create(BaseComponent.prototype);
+ReflectorComponent.prototype.constructor = ReflectorComponent;
+
+ReflectorComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'reflectorPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '反光组件'
+ }]
+ }, {
+ xtype: 'row',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '反光'
+ }, {
+ xtype: 'checkbox',
+ id: 'reflect',
+ scope: this.id,
+ onChange: this.onChangeReflect.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'colorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '颜色'
+ }, {
+ xtype: 'color',
+ id: 'color',
+ scope: this.id,
+ value: 0xffffff,
+ onChange: this.onChangeReflect.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'sizeRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '贴图尺寸'
+ }, {
+ xtype: 'select',
+ id: 'size',
+ scope: this.id,
+ options: {
+ 512: '512*512',
+ 1024: '1024*1024',
+ 2048: '2048*2048'
+ },
+ value: '1024',
+ onChange: this.onChangeReflect.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'clipBiasRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '裁剪偏移'
+ }, {
+ xtype: 'number',
+ id: 'clipBias',
+ scope: this.id,
+ value: 0,
+ onChange: this.onChangeReflect.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'recursionRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '递归'
+ }, {
+ xtype: 'checkbox',
+ id: 'recursion',
+ scope: this.id,
+ value: false,
+ onChange: this.onChangeReflect.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+ReflectorComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+ReflectorComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+ReflectorComponent.prototype.updateUI = function () {
+ var container = UI.get('reflectorPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var colorRow = UI.get('colorRow', this.id);
+ var sizeRow = UI.get('sizeRow', this.id);
+ var clipBiasRow = UI.get('clipBiasRow', this.id);
+ var recursionRow = UI.get('recursionRow', this.id);
+
+ var reflect = UI.get('reflect', this.id);
+ var color = UI.get('color', this.id);
+ var size = UI.get('size', this.id);
+ var clipBias = UI.get('clipBias', this.id);
+ var recursion = UI.get('recursion', this.id);
+
+ reflect.setValue(this.selected instanceof THREE.Reflector);
+
+ if (this.selected instanceof THREE.Reflector) {
+ colorRow.dom.style.display = '';
+ sizeRow.dom.style.display = '';
+ clipBiasRow.dom.style.display = '';
+ recursionRow.dom.style.display = '';
+ color.setHexValue(this.selected.userData.color);
+ size.setValue(this.selected.userData.size);
+ clipBias.setValue(this.selected.userData.clipBias);
+ recursion.setValue(this.selected.userData.recursion);
+ } else {
+ colorRow.dom.style.display = 'none';
+ sizeRow.dom.style.display = 'none';
+ clipBiasRow.dom.style.display = 'none';
+ recursionRow.dom.style.display = 'none';
+ }
+};
+
+ReflectorComponent.prototype.onChangeReflect = function () {
+ var reflect = UI.get('reflect', this.id);
+ var color = UI.get('color', this.id);
+ var size = UI.get('size', this.id);
+ var clipBias = UI.get('clipBias', this.id);
+ var recursion = UI.get('recursion', this.id);
+
+ var editor = this.app.editor;
+
+ if (reflect.getValue()) {
+ color = color.getHexValue();
+
+ if (!(this.selected instanceof THREE.Reflector) && !Array.isArray(this.selected.material) && this.selected.material.color) {
+ color = this.selected.material.color.getHex();
+ }
+
+ var reflector = new THREE.Reflector(this.selected.geometry, {
+ color: color,
+ textureWidth: parseInt(size.getValue()),
+ textureHeight: parseInt(size.getValue()),
+ clipBias: clipBias.getValue(),
+ recursion: recursion.getValue() ? 1 : 0
+ });
+
+ reflector.name = this.selected.name;
+ reflector.position.copy(this.selected.position);
+ reflector.rotation.copy(this.selected.rotation);
+ reflector.scale.copy(this.selected.scale);
+ reflector.castShadow = this.selected.castShadow;
+ reflector.receiveShadow = this.selected.receiveShadow;
+
+ if (this.selected instanceof THREE.Reflector) {
+ Object.assign(reflector.userData, this.selected.userData);
+ } else {
+ Object.assign(reflector.userData, this.selected.userData, {
+ mesh: this.selected
+ });
+ }
+
+ reflector.userData.color = color;
+ reflector.userData.size = size.getValue();
+ reflector.userData.clipBias = clipBias.getValue();
+ reflector.userData.recursion = recursion.getValue();
+
+ var index = editor.scene.children.indexOf(this.selected);
+ if (index > -1) {
+ editor.scene.children[index] = reflector;
+ reflector.parent = this.selected.parent;
+ this.selected.parent = null;
+ this.app.call(`objectRemoved`, this, this.selected);
+ this.app.call(`objectAdded`, this, reflector);
+ editor.select(reflector);
+ this.app.call('sceneGraphChanged', this.id);
+ }
+ } else {
+ if (this.selected instanceof THREE.Reflector) {
+ var mesh = this.selected.userData.mesh;
+ this.selected.userData.mesh = null;
+
+ mesh.name = this.selected.name;
+ mesh.position.copy(this.selected.position);
+ mesh.rotation.copy(this.selected.rotation);
+ mesh.scale.copy(this.selected.scale);
+ mesh.castShadow = this.selected.castShadow;
+ mesh.receiveShadow = this.selected.receiveShadow;
+
+ if (!Array.isArray(mesh.material) && mesh.material.color) {
+ mesh.material.color = new THREE.Color(color.getHexValue());
+ }
+
+ Object.assign(mesh.userData, this.selected.userData);
+
+ var index = editor.scene.children.indexOf(this.selected);
+ if (index > -1) {
+ editor.scene.children[index] = mesh;
+ mesh.parent = this.selected.parent;
+ this.selected.parent = null;
+ this.app.call(`objectRemoved`, this, this.selected);
+ this.app.call(`objectAdded`, this, mesh);
+ editor.select(mesh);
+ this.app.call('sceneGraphChanged', this.id);
+ }
+ }
+ }
+};
+
+export default ReflectorComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/SceneComponent.js b/ShadowEditor.Core/src/component/SceneComponent.js
new file mode 100644
index 00000000..0f7442b9
--- /dev/null
+++ b/ShadowEditor.Core/src/component/SceneComponent.js
@@ -0,0 +1,613 @@
+import BaseComponent from './BaseComponent';
+import Converter from '../utils/Converter';
+import Ajax from '../utils/Ajax';
+import TextureWindow from '../editor/window/TextureWindow';
+
+/**
+ * 场景组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SceneComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+SceneComponent.prototype = Object.create(BaseComponent.prototype);
+SceneComponent.prototype.constructor = SceneComponent;
+
+SceneComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'scenePanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '场景组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '背景'
+ }, {
+ xtype: 'select',
+ id: 'backgroundType',
+ scope: this.id,
+ options: {
+ 'Color': '纯色',
+ 'Image': '背景图片',
+ 'SkyBox': '立体贴图'
+ },
+ onChange: this.onChangeBackgroundType.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '背景颜色'
+ }, {
+ xtype: 'color',
+ id: 'backgroundColor',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundImageRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '背景图片'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundImage',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundPosXRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'x轴正向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundPosX',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundNegXRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'x轴负向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundNegX',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundPosYRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'y轴正向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundPosY',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundNegYRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'y轴负向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundNegY',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundPosZRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'z轴正向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundPosZ',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'backgroundNegZRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: 'z轴负向'
+ }, {
+ xtype: 'texture',
+ id: 'backgroundNegZ',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'cubeTextureCommandRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'button',
+ text: '获取',
+ onClick: this.onLoadCubeTexture.bind(this)
+ }, {
+ xtype: 'button',
+ text: '上传',
+ style: {
+ marginLeft: '8px'
+ },
+ onClick: this.onSaveCubeTexture.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '雾'
+ }, {
+ xtype: 'select',
+ id: 'fogType',
+ scope: this.id,
+ options: {
+ 'None': '无',
+ 'Fog': '线性',
+ 'FogExp2': '指数型'
+ },
+ onChange: this.onChangeFogType.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'fogColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾颜色'
+ }, {
+ xtype: 'color',
+ id: 'fogColor',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'fogNearRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾近点'
+ }, {
+ xtype: 'number',
+ id: 'fogNear',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'fogFarRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾远点'
+ }, {
+ xtype: 'number',
+ id: 'fogFar',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'fogDensityRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾浓度'
+ }, {
+ xtype: 'number',
+ id: 'fogDensity',
+ scope: this.id,
+ range: [0, 0.1],
+ precision: 3,
+ onChange: this.update.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+SceneComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+SceneComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+SceneComponent.prototype.updateUI = function () {
+ var container = UI.get('scenePanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Scene) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+ var scene = this.selected;
+
+ // 背景
+ var backgroundColorRow = UI.get('backgroundColorRow', this.id);
+ var backgroundImageRow = UI.get('backgroundImageRow', this.id);
+ var backgroundPosXRow = UI.get('backgroundPosXRow', this.id);
+ var backgroundNegXRow = UI.get('backgroundNegXRow', this.id);
+ var backgroundPosYRow = UI.get('backgroundPosYRow', this.id);
+ var backgroundNegYRow = UI.get('backgroundNegYRow', this.id);
+ var backgroundPosZRow = UI.get('backgroundPosZRow', this.id);
+ var backgroundNegZRow = UI.get('backgroundNegZRow', this.id);
+
+ var backgroundType = UI.get('backgroundType', this.id);
+ var backgroundColor = UI.get('backgroundColor', this.id);
+ var backgroundImage = UI.get('backgroundImage', this.id);
+ var backgroundPosX = UI.get('backgroundPosX', this.id);
+ var backgroundNegX = UI.get('backgroundNegX', this.id);
+ var backgroundPosY = UI.get('backgroundPosY', this.id);
+ var backgroundNegY = UI.get('backgroundNegY', this.id);
+ var backgroundPosZ = UI.get('backgroundPosZ', this.id);
+ var backgroundNegZ = UI.get('backgroundNegZ', this.id);
+
+ backgroundType.setValue(`${scene.background instanceof THREE.CubeTexture ? 'SkyBox' : (scene.background instanceof THREE.Texture ? 'Image' : 'Color')}`);
+
+ backgroundColorRow.dom.style.display = scene.background instanceof THREE.Color ? '' : 'none';
+ backgroundColor.setValue(`#${scene.background instanceof THREE.Color ? scene.background.getHexString() : 'aaaaaa'}`);
+
+ backgroundImageRow.dom.style.display = (scene.background instanceof THREE.Texture && !(scene.background instanceof THREE.CubeTexture)) ? '' : 'none';
+ backgroundImage.setValue((scene.background instanceof THREE.Texture && !(scene.background instanceof THREE.CubeTexture)) ? scene.background : null);
+
+ backgroundPosXRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+ backgroundNegXRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+ backgroundPosYRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+ backgroundNegYRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+ backgroundPosZRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+ backgroundNegZRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
+
+ backgroundPosX.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[0]) : null);
+ backgroundNegX.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[1]) : null);
+ backgroundPosY.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[2]) : null);
+ backgroundNegY.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[3]) : null);
+ backgroundPosZ.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[4]) : null);
+ backgroundNegZ.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[5]) : null);
+
+ // 雾效
+ var fogColorRow = UI.get('fogColorRow', this.id);
+ var fogNearRow = UI.get('fogNearRow', this.id);
+ var fogFarRow = UI.get('fogFarRow', this.id);
+ var fogDensityRow = UI.get('fogDensityRow', this.id);
+
+ var fogType = UI.get('fogType', this.id);
+ var fogColor = UI.get('fogColor', this.id);
+ var fogNear = UI.get('fogNear', this.id);
+ var fogFar = UI.get('fogFar', this.id);
+ var fogDensity = UI.get('fogDensity', this.id);
+
+ fogType.setValue(scene.fog == null ? 'None' : ((scene.fog instanceof THREE.FogExp2) ? 'FogExp2' : 'Fog'));
+
+ fogColorRow.dom.style.display = scene.fog == null ? 'none' : '';
+ fogColor.setValue(`#${scene.fog == null ? 'aaaaaa' : scene.fog.color.getHexString()}`);
+
+ fogNearRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none';
+ fogNear.setValue((scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.near : 0.1);
+
+ fogFarRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none';
+ fogFar.setValue((scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.far : 50);
+
+ fogDensityRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.FogExp2) ? '' : 'none';
+ fogDensity.setValue((scene.fog && scene.fog instanceof THREE.FogExp2) ? fog.density : 0.05);
+};
+
+SceneComponent.prototype.onChangeBackgroundType = function () { // 切换背景类型
+ var backgroundType = UI.get('backgroundType', this.id);
+
+ var backgroundColorRow = UI.get('backgroundColorRow', this.id);
+ var backgroundImageRow = UI.get('backgroundImageRow', this.id);
+ var backgroundPosXRow = UI.get('backgroundPosXRow', this.id);
+ var backgroundNegXRow = UI.get('backgroundNegXRow', this.id);
+ var backgroundPosYRow = UI.get('backgroundPosYRow', this.id);
+ var backgroundNegYRow = UI.get('backgroundNegYRow', this.id);
+ var backgroundPosZRow = UI.get('backgroundPosZRow', this.id);
+ var backgroundNegZRow = UI.get('backgroundNegZRow', this.id);
+
+ var cubeTextureCommandRow = UI.get('cubeTextureCommandRow', this.id);
+
+ switch (backgroundType.getValue()) {
+ case 'Color':
+ backgroundColorRow.dom.style.display = '';
+ backgroundImageRow.dom.style.display = 'none';
+ backgroundPosXRow.dom.style.display = 'none';
+ backgroundNegXRow.dom.style.display = 'none';
+ backgroundPosYRow.dom.style.display = 'none';
+ backgroundNegYRow.dom.style.display = 'none';
+ backgroundPosZRow.dom.style.display = 'none';
+ backgroundNegZRow.dom.style.display = 'none';
+ cubeTextureCommandRow.dom.style.display = 'none';
+ break;
+ case 'Image':
+ backgroundColorRow.dom.style.display = 'none';
+ backgroundImageRow.dom.style.display = '';
+ backgroundPosXRow.dom.style.display = 'none';
+ backgroundNegXRow.dom.style.display = 'none';
+ backgroundPosYRow.dom.style.display = 'none';
+ backgroundNegYRow.dom.style.display = 'none';
+ backgroundPosZRow.dom.style.display = 'none';
+ backgroundNegZRow.dom.style.display = 'none';
+ cubeTextureCommandRow.dom.style.display = 'none';
+ break;
+ case 'SkyBox':
+ backgroundColorRow.dom.style.display = 'none';
+ backgroundImageRow.dom.style.display = 'none';
+ backgroundPosXRow.dom.style.display = '';
+ backgroundNegXRow.dom.style.display = '';
+ backgroundPosYRow.dom.style.display = '';
+ backgroundNegYRow.dom.style.display = '';
+ backgroundPosZRow.dom.style.display = '';
+ backgroundNegZRow.dom.style.display = '';
+ cubeTextureCommandRow.dom.style.display = '';
+ break;
+ }
+
+ this.update();
+};
+
+SceneComponent.prototype.onLoadCubeTexture = function () { // 加载立体贴图
+ if (this.textureWindow === undefined) {
+ this.textureWindow = new TextureWindow({
+ app: this.app,
+ onSelect: this.onSelectCubeTexture.bind(this)
+ });
+ this.textureWindow.render();
+ }
+ this.textureWindow.show();
+};
+
+SceneComponent.prototype.onSelectCubeTexture = function (model) {
+ if (model.Type !== 'cube') {
+ UI.msg('只允许选择立体贴图!');
+ return;
+ }
+
+ var urls = model.Url.split(';');
+
+ var loader = new THREE.TextureLoader();
+
+ var promises = urls.map(url => {
+ return new Promise(resolve => {
+ loader.load(`${this.app.options.server}${url}`, texture => {
+ resolve(texture);
+ }, undefined, error => {
+ console.error(error);
+ UI.msg('立体贴图获取失败!');
+ });
+ });
+ });
+
+ Promise.all(promises).then(textures => {
+ UI.get('backgroundPosX', this.id).setValue(textures[0]);
+ UI.get('backgroundNegX', this.id).setValue(textures[1]);
+ UI.get('backgroundPosY', this.id).setValue(textures[2]);
+ UI.get('backgroundNegY', this.id).setValue(textures[3]);
+ UI.get('backgroundPosZ', this.id).setValue(textures[4]);
+ UI.get('backgroundNegZ', this.id).setValue(textures[5]);
+
+ this.textureWindow.hide();
+ this.update();
+ });
+};
+
+SceneComponent.prototype.onSaveCubeTexture = function () { // 保存立体贴图
+ var texturePosX = UI.get('backgroundPosX', this.id).getValue();
+ var textureNegX = UI.get('backgroundNegX', this.id).getValue();
+ var texturePosY = UI.get('backgroundPosY', this.id).getValue();
+ var textureNegY = UI.get('backgroundNegY', this.id).getValue();
+ var texturePosZ = UI.get('backgroundPosZ', this.id).getValue();
+ var textureNegZ = UI.get('backgroundNegZ', this.id).getValue();
+
+ if (!texturePosX || !textureNegX || !texturePosY || !textureNegY || !texturePosZ || !textureNegZ) {
+ UI.msg(`请上传所有立体贴图后再点击保存!`);
+ return;
+ }
+
+ var posXSrc = texturePosX.image.src;
+ var negXSrc = textureNegX.image.src;
+ var posYSrc = texturePosY.image.src;
+ var negYSrc = textureNegY.image.src;
+ var posZSrc = texturePosZ.image.src;
+ var negZSrc = textureNegZ.image.src;
+
+ if (posXSrc.startsWith('http') || negXSrc.startsWith('http') || posYSrc.startsWith('http') || negYSrc.startsWith('http') || posZSrc.startsWith('http') || negZSrc.startsWith('http')) {
+ UI.msg(`立体贴图已经存在于服务端,无需重复上传。`);
+ return;
+ }
+
+ // TODO: 下面代码转换出的DataURL太大,不行!!!
+
+ // 如果src是服务端地址,则需要转成DataURL
+ // if (posXSrc.startsWith('http')) {
+ // posXSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosX.image));
+ // }
+
+ // if (negXSrc.startsWith('http')) {
+ // negXSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegX.image));
+ // }
+
+ // if (posYSrc.startsWith('http')) {
+ // posYSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosY.image));
+ // }
+
+ // if (negYSrc.startsWith('http')) {
+ // negYSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegY.image));
+ // }
+
+ // if (posZSrc.startsWith('http')) {
+ // posZSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosZ.image));
+ // }
+
+ // if (negZSrc.startsWith('http')) {
+ // negZSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegZ.image));
+ // }
+
+ var promises = [
+ Converter.dataURLtoFile(posXSrc, 'posX'),
+ Converter.dataURLtoFile(negXSrc, 'negX'),
+ Converter.dataURLtoFile(posYSrc, 'posY'),
+ Converter.dataURLtoFile(negYSrc, 'negY'),
+ Converter.dataURLtoFile(posZSrc, 'posZ'),
+ Converter.dataURLtoFile(negZSrc, 'negZ'),
+ ];
+
+ Promise.all(promises).then(files => {
+ Ajax.post(`${this.app.options.server}/api/Texture/Add`, {
+ posX: files[0],
+ negX: files[1],
+ posY: files[2],
+ negY: files[3],
+ posZ: files[4],
+ negZ: files[5],
+ }, result => {
+ var obj = JSON.parse(result);
+ UI.msg(obj.Msg);
+ });
+ });
+};
+
+SceneComponent.prototype.onChangeFogType = function () { // 切换雾类型
+ var fogType = UI.get('fogType', this.id);
+ var fogColorRow = UI.get('fogColorRow', this.id);
+ var fogNearRow = UI.get('fogNearRow', this.id);
+ var fogFarRow = UI.get('fogFarRow', this.id);
+ var fogDensityRow = UI.get('fogDensityRow', this.id);
+
+ switch (fogType.getValue()) {
+ case 'None':
+ fogColorRow.dom.style.display = 'none';
+ fogNearRow.dom.style.display = 'none';
+ fogFarRow.dom.style.display = 'none';
+ fogDensityRow.dom.style.display = 'none';
+ break;
+ case 'Fog':
+ fogColorRow.dom.style.display = '';
+ fogNearRow.dom.style.display = '';
+ fogFarRow.dom.style.display = '';
+ fogDensityRow.dom.style.display = 'none';
+ break;
+ case 'FogExp2':
+ fogColorRow.dom.style.display = '';
+ fogNearRow.dom.style.display = 'none';
+ fogFarRow.dom.style.display = 'none';
+ fogDensityRow.dom.style.display = '';
+ break;
+ }
+
+ this.update();
+};
+
+SceneComponent.prototype.update = function () {
+ var scene = this.selected;
+
+ // 背景
+ var backgroundType = UI.get('backgroundType', this.id).getValue();
+ var backgroundColor = UI.get('backgroundColor', this.id).getHexValue();
+ var backgroundImage = UI.get('backgroundImage', this.id).getValue();
+ var backgroundPosX = UI.get('backgroundPosX', this.id).getValue();
+ var backgroundNegX = UI.get('backgroundNegX', this.id).getValue();
+ var backgroundPosY = UI.get('backgroundPosY', this.id).getValue();
+ var backgroundNegY = UI.get('backgroundNegY', this.id).getValue();
+ var backgroundPosZ = UI.get('backgroundPosZ', this.id).getValue();
+ var backgroundNegZ = UI.get('backgroundNegZ', this.id).getValue();
+
+ switch (backgroundType) {
+ case 'Color':
+ scene.background = new THREE.Color(backgroundColor);
+ break;
+ case 'Image':
+ if (backgroundImage) {
+ scene.background = backgroundImage;
+ }
+ break;
+ case 'SkyBox':
+ if (backgroundPosX && backgroundNegX && backgroundPosY && backgroundNegY && backgroundPosZ && backgroundNegZ) {
+ scene.background = new THREE.CubeTexture([
+ backgroundPosX.image,
+ backgroundNegX.image,
+ backgroundPosY.image,
+ backgroundNegY.image,
+ backgroundPosZ.image,
+ backgroundNegZ.image
+ ]);
+ scene.background.needsUpdate = true;
+ }
+ break;
+ }
+
+ // 雾
+ var fogType = UI.get('fogType', this.id).getValue();
+ var fogColor = UI.get('fogColor', this.id).getHexValue();
+ var fogNear = UI.get('fogNear', this.id).getValue();
+ var fogFar = UI.get('fogFar', this.id).getValue();
+ var fogDensity = UI.get('fogDensity', this.id).getValue();
+
+ switch (fogType) {
+ case 'None':
+ scene.fog = null;
+ break;
+ case 'Fog':
+ scene.fog = new THREE.Fog(fogColor, fogNear, fogFar);
+ break;
+ case 'FogExp2':
+ scene.fog = new THREE.FogExp2(fogColor, fogDensity);
+ break;
+ }
+};
+
+export default SceneComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/ShadowComponent.js b/ShadowEditor.Core/src/component/ShadowComponent.js
new file mode 100644
index 00000000..ff5505b9
--- /dev/null
+++ b/ShadowEditor.Core/src/component/ShadowComponent.js
@@ -0,0 +1,363 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 阴影组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ShadowComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+ShadowComponent.prototype = Object.create(BaseComponent.prototype);
+ShadowComponent.prototype.constructor = ShadowComponent;
+
+ShadowComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'shadowPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '阴影组件'
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectShadowRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '阴影'
+ }, {
+ xtype: 'boolean',
+ id: 'objectCastShadow',
+ scope: this.id,
+ value: false,
+ text: '产生',
+ onChange: this.onChangeCastShadow.bind(this)
+ }, {
+ xtype: 'boolean',
+ id: 'objectReceiveShadow',
+ scope: this.id,
+ value: false,
+ text: '接收',
+ onChange: this.onChangeReceiveShadow.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectShadowRadiusRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'objectShadowRadius',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeShadowRadius.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectMapSizeRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '贴图尺寸'
+ }, {
+ xtype: 'select',
+ id: 'objectMapSize',
+ scope: this.id,
+ options: {
+ 512: '512*512',
+ 1024: '1024*1024',
+ 2048: '2048*2048'
+ },
+ value: 512,
+ onChange: this.onChangeMapSize.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectBiasRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '偏差'
+ }, {
+ xtype: 'number',
+ id: 'objectBias',
+ scope: this.id,
+ value: 0,
+ range: [0, 1],
+ onChange: this.onChangeBias.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraLeftRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机左'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraLeft',
+ scope: this.id,
+ value: -5,
+ onChange: this.onChangeCameraLeft.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraRightRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机右'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraRight',
+ scope: this.id,
+ value: 5,
+ onChange: this.onChangeCameraRight.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraTopRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机上'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraTop',
+ scope: this.id,
+ value: 5,
+ onChange: this.onChangeCameraTop.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraBottomRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机下'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraBottom',
+ scope: this.id,
+ value: -5,
+ onChange: this.onChangeCameraBottom.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraNearRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机近'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraNear',
+ scope: this.id,
+ value: 0.5,
+ range: [0, Infinity],
+ onChange: this.onChangeCameraNear.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'objectCameraFarRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '相机远'
+ }, {
+ xtype: 'number',
+ id: 'objectCameraFar',
+ scope: this.id,
+ value: 0.5,
+ range: [0, Infinity],
+ onChange: this.onChangeCameraFar.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+ShadowComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+ShadowComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+ShadowComponent.prototype.updateUI = function () {
+ var container = UI.get('shadowPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && (editor.selected instanceof THREE.Mesh || editor.selected instanceof THREE.DirectionalLight || editor.selected instanceof THREE.PointLight || editor.selected instanceof THREE.SpotLight)) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var objectShadowRadiusRow = UI.get('objectShadowRadiusRow', this.id);
+ var objectMapSizeRow = UI.get('objectMapSizeRow', this.id);
+ var objectBiasRow = UI.get('objectBiasRow', this.id);
+ var objectCameraLeftRow = UI.get('objectCameraLeftRow', this.id);
+ var objectCameraRightRow = UI.get('objectCameraRightRow', this.id);
+ var objectCameraTopRow = UI.get('objectCameraTopRow', this.id);
+ var objectCameraBottomRow = UI.get('objectCameraBottomRow', this.id);
+ var objectCameraNearRow = UI.get('objectCameraNearRow', this.id);
+ var objectCameraFarRow = UI.get('objectCameraFarRow', this.id);
+
+ var objectCastShadow = UI.get('objectCastShadow', this.id);
+ var objectReceiveShadow = UI.get('objectReceiveShadow', this.id);
+ var objectShadowRadius = UI.get('objectShadowRadius', this.id);
+ var objectMapSize = UI.get('objectMapSize', this.id);
+ var objectBias = UI.get('objectBias', this.id);
+ var objectCameraLeft = UI.get('objectCameraLeft', this.id);
+ var objectCameraRight = UI.get('objectCameraRight', this.id);
+ var objectCameraTop = UI.get('objectCameraTop', this.id);
+ var objectCameraBottom = UI.get('objectCameraBottom', this.id);
+ var objectCameraNear = UI.get('objectCameraNear', this.id);
+ var objectCameraFar = UI.get('objectCameraFar', this.id);
+
+ objectCastShadow.setValue(this.selected.castShadow);
+
+ if (this.selected instanceof THREE.Light) {
+ objectReceiveShadow.dom.style.display = 'none';
+ objectShadowRadiusRow.dom.style.display = '';
+ objectMapSizeRow.dom.style.display = '';
+ objectBiasRow.dom.style.display = '';
+ objectCameraLeftRow.dom.style.display = '';
+ objectCameraRightRow.dom.style.display = '';
+ objectCameraTopRow.dom.style.display = '';
+ objectCameraBottomRow.dom.style.display = '';
+ objectCameraNearRow.dom.style.display = '';
+ objectCameraFarRow.dom.style.display = '';
+
+ objectShadowRadius.setValue(this.selected.shadow.radius);
+ var mapSize = this.selected.shadow.mapSize;
+ objectMapSize.setValue(mapSize.x);
+ objectBias.setValue(this.selected.shadow.bias);
+ objectCameraLeft.setValue(this.selected.shadow.camera.left);
+ objectCameraRight.setValue(this.selected.shadow.camera.right);
+ objectCameraTop.setValue(this.selected.shadow.camera.top);
+ objectCameraBottom.setValue(this.selected.shadow.camera.bottom);
+ objectCameraNear.setValue(this.selected.shadow.camera.near);
+ objectCameraFar.setValue(this.selected.shadow.camera.far);
+ } else {
+ objectReceiveShadow.dom.style.display = '';
+ objectShadowRadiusRow.dom.style.display = 'none';
+ objectMapSizeRow.dom.style.display = 'none';
+ objectBiasRow.dom.style.display = 'none';
+ objectCameraLeftRow.dom.style.display = 'none';
+ objectCameraRightRow.dom.style.display = 'none';
+ objectCameraTopRow.dom.style.display = 'none';
+ objectCameraBottomRow.dom.style.display = 'none';
+ objectCameraNearRow.dom.style.display = 'none';
+ objectCameraFarRow.dom.style.display = 'none';
+
+ objectReceiveShadow.setValue(this.selected.receiveShadow);
+ }
+};
+
+ShadowComponent.prototype.onChangeCastShadow = function () {
+ var objectCastShadow = UI.get('objectCastShadow', this.id);
+ this.selected.castShadow = objectCastShadow.getValue();
+ if (this.selected instanceof THREE.Mesh) {
+ this.updateMaterial(this.selected.material);
+ }
+};
+
+ShadowComponent.prototype.onChangeReceiveShadow = function () {
+ var objectReceiveShadow = UI.get('objectReceiveShadow', this.id);
+ this.selected.receiveShadow = objectReceiveShadow.getValue();
+ if (this.selected instanceof THREE.Mesh) {
+ this.updateMaterial(this.selected.material);
+ }
+};
+
+ShadowComponent.prototype.onChangeShadowRadius = function () {
+ var objectShadowRadius = UI.get('objectShadowRadius', this.id);
+ this.selected.shadow.radius = objectShadowRadius.getValue();
+};
+
+ShadowComponent.prototype.updateMaterial = function (material) {
+ if (Array.isArray(material)) {
+ material.forEach(n => {
+ n.needsUpdate = true;
+ });
+ } else {
+ material.needsUpdate = true;
+ }
+};
+
+ShadowComponent.prototype.onChangeMapSize = function () {
+ var objectMapSize = UI.get('objectMapSize', this.id);
+ var mapSize = objectMapSize.getValue();
+ this.selected.shadow.mapSize.x = this.selected.shadow.mapSize.y = parseInt(mapSize);
+};
+
+ShadowComponent.prototype.onChangeBias = function () {
+ var objectBias = UI.get('objectBias', this.id);
+ this.selected.shadow.bias = objectBias.getValue();
+};
+
+ShadowComponent.prototype.onChangeCameraLeft = function () {
+ var objectCameraLeft = UI.get('objectCameraLeft', this.id);
+ this.selected.shadow.camera.left = objectCameraLeft.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+ShadowComponent.prototype.onChangeCameraRight = function () {
+ var objectCameraRight = UI.get('objectCameraRight', this.id);
+ this.selected.shadow.camera.right = objectCameraRight.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+ShadowComponent.prototype.onChangeCameraTop = function () {
+ var objectCameraTop = UI.get('objectCameraTop', this.id);
+ this.selected.shadow.camera.top = objectCameraTop.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+ShadowComponent.prototype.onChangeCameraBottom = function () {
+ var objectCameraBottom = UI.get('objectCameraBottom', this.id);
+ this.selected.shadow.camera.bottom = objectCameraBottom.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+ShadowComponent.prototype.onChangeCameraNear = function () {
+ var objectCameraNear = UI.get('objectCameraNear', this.id);
+ this.selected.shadow.camera.near = objectCameraNear.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+ShadowComponent.prototype.onChangeCameraFar = function () {
+ var objectCameraFar = UI.get('objectCameraFar', this.id);
+ this.selected.shadow.camera.far = objectCameraFar.getValue();
+ this.selected.shadow.camera.updateProjectionMatrix();
+};
+
+export default ShadowComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/SmokeComponent.js b/ShadowEditor.Core/src/component/SmokeComponent.js
new file mode 100644
index 00000000..db899ae0
--- /dev/null
+++ b/ShadowEditor.Core/src/component/SmokeComponent.js
@@ -0,0 +1,118 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+
+/**
+ * 烟组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SmokeComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+
+ this.isPlaying = false;
+}
+
+SmokeComponent.prototype = Object.create(BaseComponent.prototype);
+SmokeComponent.prototype.constructor = SmokeComponent;
+
+SmokeComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'smokePanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '烟组件'
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label'
+ }, {
+ xtype: 'button',
+ id: 'btnPreview',
+ scope: this.id,
+ text: '预览',
+ onClick: this.onPreview.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+SmokeComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+SmokeComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+SmokeComponent.prototype.updateUI = function () {
+ var container = UI.get('smokePanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected.userData.type === 'Smoke') {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ if (this.isPlaying) {
+ btnPreview.setText('取消');
+ } else {
+ btnPreview.setText('预览');
+ }
+};
+
+SmokeComponent.prototype.onPreview = function () {
+ if (this.isPlaying) {
+ this.stopPreview();
+ } else {
+ this.startPreview();
+ }
+};
+
+SmokeComponent.prototype.startPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = true;
+ btnPreview.setText('取消');
+
+ this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
+};
+
+SmokeComponent.prototype.stopPreview = function () {
+ var btnPreview = UI.get('btnPreview', this.id);
+
+ this.isPlaying = false;
+ btnPreview.setText('预览');
+
+ this.app.on(`animate.${this.id}`, null);
+};
+
+SmokeComponent.prototype.onAnimate = function (clock, deltaTime) {
+ var elapsed = clock.getElapsedTime();
+ this.selected.update(elapsed);
+};
+
+export default SmokeComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/TransformComponent.js b/ShadowEditor.Core/src/component/TransformComponent.js
new file mode 100644
index 00000000..b61cdba3
--- /dev/null
+++ b/ShadowEditor.Core/src/component/TransformComponent.js
@@ -0,0 +1,238 @@
+import BaseComponent from './BaseComponent';
+import SetValueCommand from '../command/SetValueCommand';
+import SetPositionCommand from '../command/SetPositionCommand';
+import SetRotationCommand from '../command/SetRotationCommand';
+import SetScaleCommand from '../command/SetScaleCommand';
+
+/**
+ * 位移组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TransformComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+TransformComponent.prototype = Object.create(BaseComponent.prototype);
+TransformComponent.prototype.constructor = TransformComponent;
+
+TransformComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'transformPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '位移组件'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '平移'
+ }, {
+ xtype: 'number',
+ id: 'objectPositionX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectPositionY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangePosition.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectPositionZ',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangePosition.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '旋转'
+ }, {
+ xtype: 'number',
+ id: 'objectRotationX',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeRotation.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectRotationY',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeRotation.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectRotationZ',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeRotation.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '缩放'
+ }, {
+ xtype: 'checkbox',
+ id: 'objectScaleLock',
+ scope: this.id,
+ value: true,
+ style: {
+ position: 'absolute',
+ left: '50px'
+ }
+ }, {
+ xtype: 'number',
+ id: 'objectScaleX',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeScale.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectScaleY',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeScale.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'objectScaleZ',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChangeScale.bind(this)
+ }]
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+TransformComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+TransformComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+TransformComponent.prototype.updateUI = function () {
+ var container = UI.get('transformPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var objectPositionX = UI.get('objectPositionX', this.id);
+ var objectPositionY = UI.get('objectPositionY', this.id);
+ var objectPositionZ = UI.get('objectPositionZ', this.id);
+
+ var objectRotationX = UI.get('objectRotationX', this.id);
+ var objectRotationY = UI.get('objectRotationY', this.id);
+ var objectRotationZ = UI.get('objectRotationZ', this.id);
+
+ var objectScaleX = UI.get('objectScaleX', this.id);
+ var objectScaleY = UI.get('objectScaleY', this.id);
+ var objectScaleZ = UI.get('objectScaleZ', this.id);
+
+ objectPositionX.setValue(this.selected.position.x);
+ objectPositionY.setValue(this.selected.position.y);
+ objectPositionZ.setValue(this.selected.position.z);
+
+ objectRotationX.setValue(this.selected.rotation.x * 180 / Math.PI);
+ objectRotationY.setValue(this.selected.rotation.y * 180 / Math.PI);
+ objectRotationZ.setValue(this.selected.rotation.z * 180 / Math.PI);
+
+ objectScaleX.setValue(this.selected.scale.x);
+ objectScaleY.setValue(this.selected.scale.y);
+ objectScaleZ.setValue(this.selected.scale.z);
+};
+
+TransformComponent.prototype.onChangePosition = function () {
+ var x = UI.get('objectPositionX', this.id).getValue();
+ var y = UI.get('objectPositionY', this.id).getValue();
+ var z = UI.get('objectPositionZ', this.id).getValue();
+
+ this.app.editor.execute(new SetPositionCommand(this.selected, new THREE.Vector3(x, y, z)));
+};
+
+TransformComponent.prototype.onChangeRotation = function () {
+ var x = UI.get('objectRotationX', this.id).getValue();
+ var y = UI.get('objectRotationY', this.id).getValue();
+ var z = UI.get('objectRotationZ', this.id).getValue();
+
+ this.app.editor.execute(new SetRotationCommand(this.selected, new THREE.Euler(x * Math.PI / 180, y * Math.PI / 180, z * Math.PI / 180)));
+};
+
+TransformComponent.prototype.onChangeScale = function (value) {
+ var x = UI.get('objectScaleX', this.id).getValue();
+ var y = UI.get('objectScaleY', this.id).getValue();
+ var z = UI.get('objectScaleZ', this.id).getValue();
+ var locked = UI.get('objectScaleLock', this.id).getValue();
+
+ if (locked) {
+ this.app.editor.execute(new SetScaleCommand(this.selected, new THREE.Vector3(value, value, value)));
+ } else {
+ this.app.editor.execute(new SetScaleCommand(this.selected, new THREE.Vector3(x, y, z)));
+ }
+};
+
+export default TransformComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/animation/BasicAnimationComponent.js b/ShadowEditor.Core/src/component/animation/BasicAnimationComponent.js
new file mode 100644
index 00000000..1808c2d5
--- /dev/null
+++ b/ShadowEditor.Core/src/component/animation/BasicAnimationComponent.js
@@ -0,0 +1,194 @@
+import BaseComponent from '../BaseComponent';
+
+/**
+ * 动画基本信息组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function BasicAnimationComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+BasicAnimationComponent.prototype = Object.create(BaseComponent.prototype);
+BasicAnimationComponent.prototype.constructor = BasicAnimationComponent;
+
+BasicAnimationComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'basicAnimationPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ borderTop: 0,
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '基本信息'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id,
+ style: {
+ width: '120px'
+ },
+ onInput: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '目标'
+ }, {
+ xtype: 'input',
+ id: 'target',
+ scope: this.id,
+ disabled: true,
+ style: {
+ width: '80px',
+ marginRight: '8px'
+ }
+ }, {
+ xtype: 'button',
+ id: 'btnSetTarget',
+ scope: this.id,
+ text: '设置',
+ onClick: this.onSetTarget.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'select',
+ id: 'type',
+ scope: this.id,
+ options: {
+ Tween: '补间动画',
+ Skeletal: '骨骼动画',
+ Audio: '播放音乐',
+ Filter: '滤镜动画',
+ Particle: '粒子动画'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始时间'
+ }, {
+ xtype: 'number',
+ id: 'beginTime',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '结束时间'
+ }, {
+ xtype: 'number',
+ id: 'endTime',
+ scope: this.id,
+ range: [0, Infinity],
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`animationSelected.${this.id}`, this.onAnimationSelected.bind(this));
+ this.app.on(`animationChanged.${this.id}`, this.onAnimationChanged.bind(this));
+};
+
+BasicAnimationComponent.prototype.onAnimationSelected = function (animation) {
+ this.updateUI(animation);
+};
+
+BasicAnimationComponent.prototype.onAnimationChanged = function (animation) {
+ this.updateUI(animation);
+};
+
+BasicAnimationComponent.prototype.updateUI = function (animation) {
+ var container = UI.get('basicAnimationPanel', this.id);
+ if (animation) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.animation = animation;
+
+ var name = UI.get('name', this.id);
+ var target = UI.get('target', this.id);
+ var type = UI.get('type', this.id);
+ var beginTime = UI.get('beginTime', this.id);
+ var endTime = UI.get('endTime', this.id);
+
+ name.setValue(this.animation.name);
+
+ if (this.animation.target === null) {
+ target.setValue('(无)');
+ } else {
+ var obj = this.app.editor.objectByUuid(this.animation.target);
+ if (obj === null) {
+ target.setValue('(无)');
+ console.warn(`BasicAnimationComponent: 动作物体${this.animation.target}在场景中不存在。`);
+ } else {
+ target.setValue(obj.name);
+ }
+ }
+
+ type.setValue(this.animation.type);
+ beginTime.setValue(this.animation.beginTime);
+ endTime.setValue(this.animation.endTime);
+};
+
+BasicAnimationComponent.prototype.onSetTarget = function () {
+ var selected = this.app.editor.selected;
+ if (selected == null) {
+ this.animation.target = null;
+ } else {
+ this.animation.target = selected.uuid;
+ }
+
+ this.app.call('animationChanged', this, this.animation);
+};
+
+BasicAnimationComponent.prototype.onChange = function () {
+ var name = UI.get('name', this.id);
+ var type = UI.get('type', this.id);
+ var beginTime = UI.get('beginTime', this.id);
+ var endTime = UI.get('endTime', this.id);
+
+ this.animation.name = name.getValue();
+ this.animation.type = type.getValue();
+ this.animation.beginTime = beginTime.getValue();
+ this.animation.endTime = endTime.getValue();
+
+ this.app.call('animationChanged', this, this.animation);
+};
+
+export default BasicAnimationComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/animation/TweenAnimationComponent.js b/ShadowEditor.Core/src/component/animation/TweenAnimationComponent.js
new file mode 100644
index 00000000..88d01999
--- /dev/null
+++ b/ShadowEditor.Core/src/component/animation/TweenAnimationComponent.js
@@ -0,0 +1,556 @@
+import BaseComponent from '../BaseComponent';
+
+/**
+ * 补间动画组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TweenAnimationComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+TweenAnimationComponent.prototype = Object.create(BaseComponent.prototype);
+TweenAnimationComponent.prototype.constructor = TweenAnimationComponent;
+
+TweenAnimationComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'tweenAnimationPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '补间动画'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始状态'
+ }, {
+ xtype: 'select',
+ id: 'beginStatus',
+ scope: this.id,
+ options: {
+ Current: '当前状态',
+ Custom: '自定义'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'beginPositionRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '平移'
+ }, {
+ xtype: 'number',
+ id: 'beginPositionX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginPositionY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginPositionZ',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'beginRotationRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '旋转'
+ }, {
+ xtype: 'number',
+ id: 'beginRotationX',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginRotationY',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginRotationZ',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'beginScaleRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '缩放'
+ }, {
+ xtype: 'checkbox',
+ id: 'beginScaleLock',
+ scope: this.id,
+ value: true,
+ style: {
+ position: 'absolute',
+ left: '50px'
+ }
+ }, {
+ xtype: 'number',
+ id: 'beginScaleX',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginScaleY',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'beginScaleZ',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '插值函数'
+ }, {
+ xtype: 'select',
+ id: 'ease',
+ scope: this.id,
+ options: {
+ linear: 'Linear',
+ quadIn: 'Quad In',
+ quadOut: 'Quad Out',
+ quadInOut: 'Quad In Out',
+ cubicIn: 'Cubic In',
+ cubicOut: 'Cubic Out',
+ cubicInOut: 'Cubic InOut',
+ quartIn: 'Quart In',
+ quartOut: 'Quart Out',
+ quartInOut: 'Quart InOut',
+ quintIn: 'Quint In',
+ quintOut: 'Quint Out',
+ quintInOut: 'Quint In Out',
+ sineIn: 'Sine In',
+ sineOut: 'Sine Out',
+ sineInOut: 'Sine In Out',
+ backIn: 'Back In',
+ backOut: 'Back Out',
+ backInOut: 'Back In Out',
+ circIn: 'Circ In',
+ circOut: 'Circ Out',
+ circInOut: 'Circ In Out',
+ bounceIn: 'Bounce In',
+ bounceOut: 'Bounce Out',
+ bounceInOut: 'Bounce In Out',
+ elasticIn: 'Elastic In',
+ elasticOut: 'Elastic Out',
+ elasticInOut: 'Elastic In Out'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '结束状态'
+ }, {
+ xtype: 'select',
+ id: 'endStatus',
+ scope: this.id,
+ options: {
+ Current: '当前状态',
+ Custom: '自定义'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'endPositionRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '平移'
+ }, {
+ xtype: 'number',
+ id: 'endPositionX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endPositionY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endPositionZ',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'endRotationRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '旋转'
+ }, {
+ xtype: 'number',
+ id: 'endRotationX',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endRotationY',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endRotationZ',
+ scope: this.id,
+ step: 10,
+ unit: '°',
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'endScaleRow',
+ scope: this.id,
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'label',
+ text: '缩放'
+ }, {
+ xtype: 'checkbox',
+ id: 'endScaleLock',
+ scope: this.id,
+ value: true,
+ style: {
+ position: 'absolute',
+ left: '50px'
+ }
+ }, {
+ xtype: 'number',
+ id: 'endScaleX',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endScaleY',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'endScaleZ',
+ scope: this.id,
+ value: 1,
+ range: [0.01, Infinity],
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`animationSelected.${this.id}`, this.onAnimationSelected.bind(this));
+ this.app.on(`animationChanged.${this.id}`, this.onAnimationChanged.bind(this));
+};
+
+TweenAnimationComponent.prototype.onAnimationSelected = function (animation) {
+ this.updateUI(animation);
+};
+
+TweenAnimationComponent.prototype.onAnimationChanged = function (animation) {
+ this.updateUI(animation);
+};
+
+TweenAnimationComponent.prototype.updateUI = function (animation) {
+ var container = UI.get('tweenAnimationPanel', this.id);
+ if (animation && animation.type === 'Tween') {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.animation = animation;
+
+ var beginPositionRow = UI.get('beginPositionRow', this.id);
+ var beginRotationRow = UI.get('beginRotationRow', this.id);
+ var beginScaleRow = UI.get('beginScaleRow', this.id);
+ var endPositionRow = UI.get('endPositionRow', this.id);
+ var endRotationRow = UI.get('endRotationRow', this.id);
+ var endScaleRow = UI.get('endScaleRow', this.id);
+
+ var beginStatus = UI.get('beginStatus', this.id);
+ var beginPositionX = UI.get('beginPositionX', this.id);
+ var beginPositionY = UI.get('beginPositionY', this.id);
+ var beginPositionZ = UI.get('beginPositionZ', this.id);
+ var beginRotationX = UI.get('beginRotationX', this.id);
+ var beginRotationY = UI.get('beginRotationY', this.id);
+ var beginRotationZ = UI.get('beginRotationZ', this.id);
+ var beginScaleLock = UI.get('beginScaleLock', this.id);
+ var beginScaleX = UI.get('beginScaleX', this.id);
+ var beginScaleY = UI.get('beginScaleY', this.id);
+ var beginScaleZ = UI.get('beginScaleZ', this.id);
+ var ease = UI.get('ease', this.id);
+ var endStatus = UI.get('endStatus', this.id);
+ var endPositionX = UI.get('endPositionX', this.id);
+ var endPositionY = UI.get('endPositionY', this.id);
+ var endPositionZ = UI.get('endPositionZ', this.id);
+ var endRotationX = UI.get('endRotationX', this.id);
+ var endRotationY = UI.get('endRotationY', this.id);
+ var endRotationZ = UI.get('endRotationZ', this.id);
+ var endScaleLock = UI.get('endScaleLock', this.id);
+ var endScaleX = UI.get('endScaleX', this.id);
+ var endScaleY = UI.get('endScaleY', this.id);
+ var endScaleZ = UI.get('endScaleZ', this.id);
+
+ switch (this.animation.beginStatus) {
+ case 'Current':
+ beginPositionRow.dom.style.display = 'none';
+ beginRotationRow.dom.style.display = 'none';
+ beginScaleRow.dom.style.display = 'none';
+ break;
+ case 'Custom':
+ beginPositionRow.dom.style.display = '';
+ beginRotationRow.dom.style.display = '';
+ beginScaleRow.dom.style.display = '';
+ break;
+ }
+
+ switch (this.animation.endStatus) {
+ case 'Current':
+ endPositionRow.dom.style.display = 'none';
+ endRotationRow.dom.style.display = 'none';
+ endScaleRow.dom.style.display = 'none';
+ break;
+ case 'Custom':
+ endPositionRow.dom.style.display = '';
+ endRotationRow.dom.style.display = '';
+ endScaleRow.dom.style.display = '';
+ break;
+ }
+
+ beginStatus.setValue(this.animation.beginStatus);
+ beginPositionX.setValue(this.animation.beginPositionX);
+ beginPositionY.setValue(this.animation.beginPositionY);
+ beginPositionZ.setValue(this.animation.beginPositionZ);
+ beginRotationX.setValue(this.animation.beginRotationX * 180 / Math.PI);
+ beginRotationY.setValue(this.animation.beginRotationY * 180 / Math.PI);
+ beginRotationZ.setValue(this.animation.beginRotationZ * 180 / Math.PI);
+ beginScaleLock.setValue(this.animation.beginScaleLock);
+ beginScaleX.setValue(this.animation.beginScaleX);
+ beginScaleY.setValue(this.animation.beginScaleY);
+ beginScaleZ.setValue(this.animation.beginScaleZ);
+ ease.setValue(this.animation.ease);
+ endStatus.setValue(this.animation.endStatus);
+ endPositionX.setValue(this.animation.endPositionX);
+ endPositionY.setValue(this.animation.endPositionY);
+ endPositionZ.setValue(this.animation.endPositionZ);
+ endRotationX.setValue(this.animation.endRotationX * 180 / Math.PI);
+ endRotationY.setValue(this.animation.endRotationY * 180 / Math.PI);
+ endRotationZ.setValue(this.animation.endRotationZ * 180 / Math.PI);
+ endScaleLock.setValue(this.animation.endScaleLock);
+ endScaleX.setValue(this.animation.endScaleX);
+ endScaleY.setValue(this.animation.endScaleY);
+ endScaleZ.setValue(this.animation.endScaleZ);
+};
+
+TweenAnimationComponent.prototype.onChange = function () {
+ var beginPositionRow = UI.get('beginPositionRow', this.id);
+ var beginRotationRow = UI.get('beginRotationRow', this.id);
+ var beginScaleRow = UI.get('beginScaleRow', this.id);
+ var endPositionRow = UI.get('endPositionRow', this.id);
+ var endRotationRow = UI.get('endRotationRow', this.id);
+ var endScaleRow = UI.get('endScaleRow', this.id);
+
+ var beginStatus = UI.get('beginStatus', this.id);
+ var beginPositionX = UI.get('beginPositionX', this.id);
+ var beginPositionY = UI.get('beginPositionY', this.id);
+ var beginPositionZ = UI.get('beginPositionZ', this.id);
+ var beginRotationX = UI.get('beginRotationX', this.id);
+ var beginRotationY = UI.get('beginRotationY', this.id);
+ var beginRotationZ = UI.get('beginRotationZ', this.id);
+ var beginScaleLock = UI.get('beginScaleLock', this.id);
+ var beginScaleX = UI.get('beginScaleX', this.id);
+ var beginScaleY = UI.get('beginScaleY', this.id);
+ var beginScaleZ = UI.get('beginScaleZ', this.id);
+ var ease = UI.get('ease', this.id);
+ var endStatus = UI.get('endStatus', this.id);
+ var endPositionX = UI.get('endPositionX', this.id);
+ var endPositionY = UI.get('endPositionY', this.id);
+ var endPositionZ = UI.get('endPositionZ', this.id);
+ var endRotationX = UI.get('endRotationX', this.id);
+ var endRotationY = UI.get('endRotationY', this.id);
+ var endRotationZ = UI.get('endRotationZ', this.id);
+ var endScaleLock = UI.get('endScaleLock', this.id);
+ var endScaleX = UI.get('endScaleX', this.id);
+ var endScaleY = UI.get('endScaleY', this.id);
+ var endScaleZ = UI.get('endScaleZ', this.id);
+
+ switch (beginStatus.getValue()) {
+ case 'Current':
+ beginPositionRow.dom.style.display = 'none';
+ beginRotationRow.dom.style.display = 'none';
+ beginScaleRow.dom.style.display = 'none';
+ break;
+ case 'Custom':
+ beginPositionRow.dom.style.display = '';
+ beginRotationRow.dom.style.display = '';
+ beginScaleRow.dom.style.display = '';
+ break;
+ }
+
+ switch (endStatus.getValue()) {
+ case 'Current':
+ endPositionRow.dom.style.display = 'none';
+ endRotationRow.dom.style.display = 'none';
+ endScaleRow.dom.style.display = 'none';
+ break;
+ case 'Custom':
+ endPositionRow.dom.style.display = '';
+ endRotationRow.dom.style.display = '';
+ endScaleRow.dom.style.display = '';
+ break;
+ }
+
+ this.animation.beginStatus = beginStatus.getValue();
+ this.animation.beginPositionX = beginPositionX.getValue();
+ this.animation.beginPositionY = beginPositionY.getValue();
+ this.animation.beginPositionZ = beginPositionZ.getValue();
+ this.animation.beginRotationX = beginRotationX.getValue() * Math.PI / 180;
+ this.animation.beginRotationY = beginRotationY.getValue() * Math.PI / 180;
+ this.animation.beginRotationZ = beginRotationZ.getValue() * Math.PI / 180;
+ this.animation.beginScaleLock = beginScaleLock.getValue();
+ this.animation.beginScaleX = beginScaleX.getValue();
+ this.animation.beginScaleY = beginScaleY.getValue();
+ this.animation.beginScaleZ = beginScaleZ.getValue();
+ this.animation.ease = ease.getValue();
+ this.animation.endStatus = endStatus.getValue();
+ this.animation.endPositionX = endPositionX.getValue();
+ this.animation.endPositionY = endPositionY.getValue();
+ this.animation.endPositionZ = endPositionZ.getValue();
+ this.animation.endRotationX = endRotationX.getValue() * Math.PI / 180;
+ this.animation.endRotationY = endRotationY.getValue() * Math.PI / 180;
+ this.animation.endRotationZ = endRotationZ.getValue() * Math.PI / 180;
+ this.animation.endScaleLock = endScaleLock.getValue();
+ this.animation.endScaleX = endScaleX.getValue();
+ this.animation.endScaleY = endScaleY.getValue();
+ this.animation.endScaleZ = endScaleZ.getValue();
+
+ this.app.call('animationChanged', this, this.animation);
+};
+
+export default TweenAnimationComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/audio/AudioListenerComponent.js b/ShadowEditor.Core/src/component/audio/AudioListenerComponent.js
new file mode 100644
index 00000000..7c03124d
--- /dev/null
+++ b/ShadowEditor.Core/src/component/audio/AudioListenerComponent.js
@@ -0,0 +1,92 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 音频监听器组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function AudioListenerComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+AudioListenerComponent.prototype = Object.create(BaseComponent.prototype);
+AudioListenerComponent.prototype.constructor = AudioListenerComponent;
+
+AudioListenerComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'audioListenerPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '音频监听器'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '主音量'
+ }, {
+ xtype: 'number',
+ id: 'masterVolume',
+ scope: this.id,
+ range: [0, 1],
+ value: 1,
+ onChange: this.onChangeMasterVolume.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+AudioListenerComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+AudioListenerComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+AudioListenerComponent.prototype.updateUI = function () {
+ var container = UI.get('audioListenerPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.PerspectiveCamera && editor.selected.children.indexOf(editor.audioListener) > -1) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.audioListener;
+
+ var masterVolume = UI.get('masterVolume', this.id);
+
+ masterVolume.setValue(this.selected.getMasterVolume());
+};
+
+AudioListenerComponent.prototype.onChangeMasterVolume = function () {
+ var masterVolume = UI.get('masterVolume', this.id);
+
+ this.selected.setMasterVolume(masterVolume.getValue());
+};
+
+export default AudioListenerComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/audio/BackgroundMusicComponent.js b/ShadowEditor.Core/src/component/audio/BackgroundMusicComponent.js
new file mode 100644
index 00000000..4c79b34a
--- /dev/null
+++ b/ShadowEditor.Core/src/component/audio/BackgroundMusicComponent.js
@@ -0,0 +1,216 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+import AudioWindow from '../../editor/window/AudioWindow';
+
+/**
+ * 背景音乐组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function BackgroundMusicComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+BackgroundMusicComponent.prototype = Object.create(BaseComponent.prototype);
+BackgroundMusicComponent.prototype.constructor = BackgroundMusicComponent;
+
+BackgroundMusicComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'backgroundMusicPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '背景音乐'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '音频'
+ }, {
+ xtype: 'text',
+ id: 'name',
+ scope: this.id,
+ text: '(无)',
+ style: {
+ width: '80px',
+ border: '1px solid #ddd',
+ marginRight: '8px'
+ }
+ }, {
+ xtype: 'button',
+ text: '选择',
+ onClick: this.onSelect.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '自动播放'
+ }, {
+ xtype: 'checkbox',
+ id: 'autoplay',
+ scope: this.id,
+ value: false,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '循环播放'
+ }, {
+ xtype: 'checkbox',
+ id: 'loop',
+ scope: this.id,
+ value: true,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '音量'
+ }, {
+ xtype: 'number',
+ id: 'volumn',
+ scope: this.id,
+ range: [0, 1],
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label'
+ }, {
+ xtype: 'button',
+ id: 'btnPlay',
+ scope: this.id,
+ text: '播放',
+ style: {
+ display: 'none'
+ },
+ onClick: this.onPlay.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+BackgroundMusicComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+BackgroundMusicComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+BackgroundMusicComponent.prototype.updateUI = function () {
+ var container = UI.get('backgroundMusicPanel', this.id);
+
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Audio) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var name = UI.get('name', this.id);
+ var autoplay = UI.get('autoplay', this.id);
+ var loop = UI.get('loop', this.id);
+ var volumn = UI.get('volumn', this.id);
+ var btnPlay = UI.get('btnPlay', this.id);
+
+ name.setValue(this.selected.userData.Name);
+ autoplay.setValue(this.selected.userData.autoplay);
+ loop.setValue(this.selected.getLoop());
+ volumn.setValue(this.selected.getVolume());
+
+ if (this.selected.buffer) {
+ btnPlay.dom.style.display = '';
+ if (this.selected.isPlaying) {
+ btnPlay.setText('停止');
+ } else {
+ btnPlay.setText('播放');
+ }
+ } else {
+ btnPlay.dom.style.display = 'none';
+ }
+};
+
+BackgroundMusicComponent.prototype.onSelect = function () {
+ if (this.window === undefined) {
+ this.window = new AudioWindow({
+ app: this.app,
+ onSelect: this.onChange.bind(this)
+ });
+ this.window.render();
+ }
+ this.window.show();
+};
+
+BackgroundMusicComponent.prototype.onChange = function (obj) {
+ var name = UI.get('name', this.id);
+ var autoplay = UI.get('autoplay', this.id);
+ var loop = UI.get('loop', this.id);
+ var volumn = UI.get('volumn', this.id);
+ var btnPlay = UI.get('btnPlay', this.id);
+
+ if (obj) { // 仅选择窗口会传递obj参数
+ Object.assign(this.selected.userData, obj);
+ var loader = new THREE.AudioLoader();
+ loader.load(obj.Url, buffer => {
+ this.selected.setBuffer(buffer);
+ btnPlay.dom.style.display = '';
+ });
+ }
+
+ this.selected.userData.autoplay = autoplay.getValue(); // 这里不能给this.selected赋值,否则音频会自动播放
+ this.selected.setLoop(loop.getValue());
+ this.selected.setVolume(volumn.getValue());
+ this.updateUI();
+
+ if (this.window) {
+ this.window.hide();
+ }
+};
+
+BackgroundMusicComponent.prototype.onPlay = function () {
+ var btnPlay = UI.get('btnPlay', this.id);
+
+ if (this.selected.buffer) {
+ btnPlay.dom.style.display = '';
+ if (this.selected.isPlaying) {
+ this.selected.stop();
+ btnPlay.setText('播放');
+ } else {
+ this.selected.play();
+ btnPlay.setText('停止');
+ }
+ } else {
+ btnPlay.dom.style.display = 'none';
+ }
+};
+
+export default BackgroundMusicComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/BoxGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/BoxGeometryComponent.js
new file mode 100644
index 00000000..d725e68a
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/BoxGeometryComponent.js
@@ -0,0 +1,169 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 正方体组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function BoxGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+BoxGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+BoxGeometryComponent.prototype.constructor = BoxGeometryComponent;
+
+BoxGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'number',
+ id: 'width',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度'
+ }, {
+ xtype: 'number',
+ id: 'height',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度'
+ }, {
+ xtype: 'number',
+ id: 'depth',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度分段'
+ }, {
+ xtype: 'int',
+ id: 'widthSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度分段'
+ }, {
+ xtype: 'int',
+ id: 'heightSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度分段'
+ }, {
+ xtype: 'int',
+ id: 'depthSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+BoxGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+BoxGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+BoxGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.BoxBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+
+ width.setValue(this.selected.geometry.parameters.width);
+ height.setValue(this.selected.geometry.parameters.height);
+ depth.setValue(this.selected.geometry.parameters.depth);
+ widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
+ heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
+ depthSegments.setValue(this.selected.geometry.parameters.depthSegments);
+};
+
+BoxGeometryComponent.prototype.onChangeGeometry = function () {
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.BoxBufferGeometry(
+ width.getValue(),
+ height.getValue(),
+ depth.getValue(),
+ widthSegments.getValue(),
+ heightSegments.getValue(),
+ depthSegments.getValue()
+ )));
+};
+
+export default BoxGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/CircleGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/CircleGeometryComponent.js
new file mode 100644
index 00000000..d0be6102
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/CircleGeometryComponent.js
@@ -0,0 +1,132 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 圆形组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function CircleGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+CircleGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+CircleGeometryComponent.prototype.constructor = CircleGeometryComponent;
+
+CircleGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'radius',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '分段'
+ }, {
+ xtype: 'int',
+ id: 'segments',
+ scope: this.id,
+ value: 16,
+ range: [3, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始弧度'
+ }, {
+ xtype: 'number',
+ id: 'thetaStart',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '转过弧度'
+ }, {
+ xtype: 'number',
+ id: 'thetaLength',
+ scope: this.id,
+ value: Math.PI * 2,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+CircleGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+CircleGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+CircleGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.CircleBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radius = UI.get('radius', this.id);
+ var segments = UI.get('segments', this.id);
+ var thetaStart = UI.get('thetaStart', this.id);
+ var thetaLength = UI.get('thetaLength', this.id);
+
+ radius.setValue(this.selected.geometry.parameters.radius);
+ segments.setValue(this.selected.geometry.parameters.segments);
+ thetaStart.setValue(this.selected.geometry.parameters.thetaStart === undefined ? 0 : this.selected.geometry.parameters.thetaStart);
+ thetaLength.setValue(this.selected.geometry.parameters.thetaLength === undefined ? Math.PI * 2 : this.selected.geometry.parameters.thetaLength);
+};
+
+CircleGeometryComponent.prototype.onChangeGeometry = function () {
+ var radius = UI.get('radius', this.id);
+ var segments = UI.get('segments', this.id);
+ var thetaStart = UI.get('thetaStart', this.id);
+ var thetaLength = UI.get('thetaLength', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.CircleBufferGeometry(
+ radius.getValue(),
+ segments.getValue(),
+ thetaStart.getValue(),
+ thetaLength.getValue()
+ )));
+};
+
+export default CircleGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/CylinderGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/CylinderGeometryComponent.js
new file mode 100644
index 00000000..ac91cdad
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/CylinderGeometryComponent.js
@@ -0,0 +1,165 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 圆柱组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function CylinderGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+CylinderGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+CylinderGeometryComponent.prototype.constructor = CylinderGeometryComponent;
+
+CylinderGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '顶部半径'
+ }, {
+ xtype: 'number',
+ id: 'radiusTop',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '底部半径'
+ }, {
+ xtype: 'number',
+ id: 'radiusBottom',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度'
+ }, {
+ xtype: 'number',
+ id: 'height',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '圆形分段'
+ }, {
+ xtype: 'int',
+ id: 'radialSegments',
+ scope: this.id,
+ value: 16,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度分段'
+ }, {
+ xtype: 'int',
+ id: 'heightSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '两端开口'
+ }, {
+ xtype: 'checkbox',
+ id: 'openEnded',
+ scope: this.id,
+ value: false,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+CylinderGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+CylinderGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+CylinderGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.CylinderBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radiusTop = UI.get('radiusTop', this.id);
+ var radiusBottom = UI.get('radiusBottom', this.id);
+ var height = UI.get('height', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var openEnded = UI.get('openEnded', this.id);
+
+ radiusTop.setValue(this.selected.geometry.parameters.radiusTop);
+ radiusBottom.setValue(this.selected.geometry.parameters.radiusBottom);
+ height.setValue(this.selected.geometry.parameters.height);
+ radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
+ heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
+ openEnded.setValue(this.selected.geometry.parameters.openEnded);
+};
+
+CylinderGeometryComponent.prototype.onChangeGeometry = function () {
+ var radiusTop = UI.get('radiusTop', this.id);
+ var radiusBottom = UI.get('radiusBottom', this.id);
+ var height = UI.get('height', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var openEnded = UI.get('openEnded', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.CylinderBufferGeometry(
+ radiusTop.getValue(),
+ radiusBottom.getValue(),
+ height.getValue(),
+ radialSegments.getValue(),
+ heightSegments.getValue(),
+ openEnded.getValue()
+ )));
+};
+
+export default CylinderGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/IcosahedronGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/IcosahedronGeometryComponent.js
new file mode 100644
index 00000000..23ed6d77
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/IcosahedronGeometryComponent.js
@@ -0,0 +1,100 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 二十面体组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function IcosahedronGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+IcosahedronGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+IcosahedronGeometryComponent.prototype.constructor = IcosahedronGeometryComponent;
+
+IcosahedronGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'radius',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '面片分段'
+ }, {
+ xtype: 'int',
+ id: 'detail',
+ scope: this.id,
+ value: 1,
+ range: [0, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+IcosahedronGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+IcosahedronGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+IcosahedronGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.IcosahedronBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radius = UI.get('radius', this.id);
+ var detail = UI.get('detail', this.id);
+
+ radius.setValue(this.selected.geometry.parameters.radius);
+ detail.setValue(this.selected.geometry.parameters.detail);
+};
+
+IcosahedronGeometryComponent.prototype.onChangeGeometry = function () {
+ var radius = UI.get('radius', this.id);
+ var detail = UI.get('detail', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.IcosahedronBufferGeometry(
+ radius.getValue(),
+ detail.getValue()
+ )));
+};
+
+export default IcosahedronGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/LatheGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/LatheGeometryComponent.js
new file mode 100644
index 00000000..ad11e58a
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/LatheGeometryComponent.js
@@ -0,0 +1,118 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 车床组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function LatheGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+LatheGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+LatheGeometryComponent.prototype.constructor = LatheGeometryComponent;
+
+LatheGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '径向分段'
+ }, {
+ xtype: 'int',
+ id: 'segments',
+ scope: this.id,
+ value: 16,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始弧度'
+ }, {
+ xtype: 'number',
+ id: 'phiStart',
+ scope: this.id,
+ value: 0,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '转过弧度'
+ }, {
+ xtype: 'number',
+ id: 'phiLength',
+ scope: this.id,
+ value: Math.PI * 2,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+LatheGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+LatheGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+LatheGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.LatheBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var segments = UI.get('segments', this.id);
+ var phiStart = UI.get('phiStart', this.id);
+ var phiLength = UI.get('phiLength', this.id);
+
+ segments.setValue(this.selected.geometry.parameters.segments);
+ phiStart.setValue(this.selected.geometry.parameters.phiStart);
+ phiLength.setValue(this.selected.geometry.parameters.phiLength);
+};
+
+LatheGeometryComponent.prototype.onChangeGeometry = function () {
+ var segments = UI.get('segments', this.id);
+ var phiStart = UI.get('phiStart', this.id);
+ var phiLength = UI.get('phiLength', this.id);
+
+ var points = this.selected.geometry.parameters.points;
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.LatheBufferGeometry(
+ points,
+ segments.getValue(),
+ phiStart.getValue(),
+ phiLength.getValue()
+ )));
+};
+
+export default LatheGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/PlaneGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/PlaneGeometryComponent.js
new file mode 100644
index 00000000..becc90da
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/PlaneGeometryComponent.js
@@ -0,0 +1,136 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 平板组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function PlaneGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+PlaneGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+PlaneGeometryComponent.prototype.constructor = PlaneGeometryComponent;
+
+PlaneGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'number',
+ id: 'width',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度'
+ }, {
+ xtype: 'number',
+ id: 'height',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度分段'
+ }, {
+ xtype: 'int',
+ id: 'widthSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度分段'
+ }, {
+ xtype: 'int',
+ id: 'heightSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+PlaneGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+PlaneGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+PlaneGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.PlaneBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+
+ width.setValue(this.selected.geometry.parameters.width);
+ height.setValue(this.selected.geometry.parameters.height);
+ widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
+ heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
+};
+
+PlaneGeometryComponent.prototype.onChangeGeometry = function () {
+ var width = UI.get('width', this.id);
+ var height = UI.get('height', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.PlaneBufferGeometry(
+ width.getValue(),
+ height.getValue(),
+ widthSegments.getValue(),
+ heightSegments.getValue()
+ )));
+};
+
+export default PlaneGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/SphereGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/SphereGeometryComponent.js
new file mode 100644
index 00000000..7cfcbab2
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/SphereGeometryComponent.js
@@ -0,0 +1,181 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 球体组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SphereGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+SphereGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+SphereGeometryComponent.prototype.constructor = SphereGeometryComponent;
+
+SphereGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'radius',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度分段'
+ }, {
+ xtype: 'int',
+ id: 'widthSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '高度分段'
+ }, {
+ xtype: 'int',
+ id: 'heightSegments',
+ scope: this.id,
+ value: 1,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始经度'
+ }, {
+ xtype: 'number',
+ id: 'phiStart',
+ scope: this.id,
+ value: 0,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '转过经度'
+ }, {
+ xtype: 'number',
+ id: 'phiLength',
+ scope: this.id,
+ value: Math.PI * 2,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开始纬度'
+ }, {
+ xtype: 'number',
+ id: 'thetaStart',
+ scope: this.id,
+ value: 0,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '转过纬度'
+ }, {
+ xtype: 'number',
+ id: 'thetaLength',
+ scope: this.id,
+ value: Math.PI / 2,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+SphereGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+SphereGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+SphereGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.SphereBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radius = UI.get('radius', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var phiStart = UI.get('phiStart', this.id);
+ var phiLength = UI.get('phiLength', this.id);
+ var thetaStart = UI.get('thetaStart', this.id);
+ var thetaLength = UI.get('thetaLength', this.id);
+
+ radius.setValue(this.selected.geometry.parameters.radius);
+ widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
+ heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
+ phiStart.setValue(this.selected.geometry.parameters.phiStart);
+ phiLength.setValue(this.selected.geometry.parameters.phiLength);
+ thetaStart.setValue(this.selected.geometry.parameters.thetaStart);
+ thetaLength.setValue(this.selected.geometry.parameters.thetaLength);
+};
+
+SphereGeometryComponent.prototype.onChangeGeometry = function () {
+ var radius = UI.get('radius', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var heightSegments = UI.get('heightSegments', this.id);
+ var phiStart = UI.get('phiStart', this.id);
+ var phiLength = UI.get('phiLength', this.id);
+ var thetaStart = UI.get('thetaStart', this.id);
+ var thetaLength = UI.get('thetaLength', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.SphereBufferGeometry(
+ radius.getValue(),
+ widthSegments.getValue(),
+ heightSegments.getValue(),
+ phiStart.getValue(),
+ phiLength.getValue(),
+ thetaStart.getValue(),
+ thetaLength.getValue()
+ )));
+};
+
+export default SphereGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/TeapotGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/TeapotGeometryComponent.js
new file mode 100644
index 00000000..ae94331f
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/TeapotGeometryComponent.js
@@ -0,0 +1,197 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 茶壶组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TeapotGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+TeapotGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+TeapotGeometryComponent.prototype.constructor = TeapotGeometryComponent;
+
+TeapotGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '尺寸'
+ }, {
+ xtype: 'number',
+ id: 'size',
+ scope: this.id,
+ value: 3,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '分段'
+ }, {
+ xtype: 'int',
+ id: 'segments',
+ scope: this.id,
+ value: 10,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '壶底'
+ }, {
+ xtype: 'checkbox',
+ id: 'bottom',
+ scope: this.id,
+ value: true,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '壶盖'
+ }, {
+ xtype: 'checkbox',
+ id: 'lid',
+ scope: this.id,
+ value: true,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '壶身'
+ }, {
+ xtype: 'checkbox',
+ id: 'body',
+ scope: this.id,
+ value: true,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '壶盖填满'
+ }, {
+ xtype: 'checkbox',
+ id: 'fitLid',
+ scope: this.id,
+ value: true,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '布林'
+ }, {
+ xtype: 'checkbox',
+ id: 'blinn',
+ scope: this.id,
+ value: true,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+TeapotGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+TeapotGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+TeapotGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TeapotBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var size = UI.get('size', this.id);
+ var segments = UI.get('segments', this.id);
+ var bottom = UI.get('bottom', this.id);
+ var lid = UI.get('lid', this.id);
+ var body = UI.get('body', this.id);
+ var fitLid = UI.get('fitLid', this.id);
+ var blinn = UI.get('blinn', this.id);
+
+ size.setValue(this.selected.geometry.parameters.size);
+ segments.setValue(this.selected.geometry.parameters.segments);
+ bottom.setValue(this.selected.geometry.parameters.bottom);
+ lid.setValue(this.selected.geometry.parameters.lid);
+ body.setValue(this.selected.geometry.parameters.body);
+ fitLid.setValue(this.selected.geometry.parameters.fitLid);
+ blinn.setValue(this.selected.geometry.parameters.blinn);
+};
+
+TeapotGeometryComponent.prototype.onChangeGeometry = function () {
+ var size = UI.get('size', this.id);
+ var segments = UI.get('segments', this.id);
+ var bottom = UI.get('bottom', this.id);
+ var lid = UI.get('lid', this.id);
+ var body = UI.get('body', this.id);
+ var fitLid = UI.get('fitLid', this.id);
+ var blinn = UI.get('blinn', this.id);
+
+ var geometry = new THREE.TeapotBufferGeometry(
+ size.getValue(),
+ segments.getValue(),
+ bottom.getValue(),
+ lid.getValue(),
+ body.getValue(),
+ fitLid.getValue(),
+ blinn.getValue()
+ );
+
+ geometry.type = 'TeapotBufferGeometry';
+
+ geometry.parameters = {
+ size: size.getValue(),
+ segments: segments.getValue(),
+ bottom: bottom.getValue(),
+ lid: lid.getValue(),
+ body: body.getValue(),
+ fitLid: fitLid.getValue(),
+ blinn: blinn.getValue()
+ };
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, geometry));
+};
+
+export default TeapotGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/TorusGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/TorusGeometryComponent.js
new file mode 100644
index 00000000..cff5f8d9
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/TorusGeometryComponent.js
@@ -0,0 +1,149 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 花托组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TorusGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+TorusGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+TorusGeometryComponent.prototype.constructor = TorusGeometryComponent;
+
+TorusGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'radius',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管粗'
+ }, {
+ xtype: 'number',
+ id: 'tube',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管粗分段'
+ }, {
+ xtype: 'int',
+ id: 'radialSegments',
+ scope: this.id,
+ value: 16,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径分段'
+ }, {
+ xtype: 'int',
+ id: 'tubularSegments',
+ scope: this.id,
+ value: 16,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '旋转弧度'
+ }, {
+ xtype: 'number',
+ id: 'arc',
+ scope: this.id,
+ value: Math.PI * 2,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+TorusGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+TorusGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+TorusGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TorusBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radius = UI.get('radius', this.id);
+ var tube = UI.get('tube', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var tubularSegments = UI.get('tubularSegments', this.id);
+ var arc = UI.get('arc', this.id);
+
+ radius.setValue(this.selected.geometry.parameters.radius);
+ tube.setValue(this.selected.geometry.parameters.tube);
+ radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
+ tubularSegments.setValue(this.selected.geometry.parameters.tubularSegments);
+ arc.setValue(this.selected.geometry.parameters.arc);
+};
+
+TorusGeometryComponent.prototype.onChangeGeometry = function () {
+ var radius = UI.get('radius', this.id);
+ var tube = UI.get('tube', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var tubularSegments = UI.get('tubularSegments', this.id);
+ var arc = UI.get('arc', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.TorusBufferGeometry(
+ radius.getValue(),
+ tube.getValue(),
+ radialSegments.getValue(),
+ tubularSegments.getValue(),
+ arc.getValue()
+ )));
+};
+
+export default TorusGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/geometry/TorusKnotGeometryComponent.js b/ShadowEditor.Core/src/component/geometry/TorusKnotGeometryComponent.js
new file mode 100644
index 00000000..9ba36a2d
--- /dev/null
+++ b/ShadowEditor.Core/src/component/geometry/TorusKnotGeometryComponent.js
@@ -0,0 +1,165 @@
+import BaseComponent from '../BaseComponent';
+import SetGeometryCommand from '../../command/SetGeometryCommand';
+
+/**
+ * 环面纽结组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TorusKnotGeometryComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+TorusKnotGeometryComponent.prototype = Object.create(BaseComponent.prototype);
+TorusKnotGeometryComponent.prototype.constructor = TorusKnotGeometryComponent;
+
+TorusKnotGeometryComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'geometryPanel',
+ scope: this.id,
+ style: {
+ borderTop: 0,
+ marginTop: '8px',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半径'
+ }, {
+ xtype: 'number',
+ id: 'radius',
+ scope: this.id,
+ value: 16,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管粗'
+ }, {
+ xtype: 'number',
+ id: 'tube',
+ scope: this.id,
+ value: 1,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管长分段'
+ }, {
+ xtype: 'int',
+ id: 'tubularSegments',
+ scope: this.id,
+ value: 16,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管粗分段'
+ }, {
+ xtype: 'int',
+ id: 'radialSegments',
+ scope: this.id,
+ value: 16,
+ range: [1, Infinity],
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '管长弧度'
+ }, {
+ xtype: 'number',
+ id: 'p',
+ scope: this.id,
+ value: 20,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '扭曲弧度'
+ }, {
+ xtype: 'number',
+ id: 'q',
+ scope: this.id,
+ value: 20,
+ onChange: this.onChangeGeometry.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+TorusKnotGeometryComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+TorusKnotGeometryComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+TorusKnotGeometryComponent.prototype.updateUI = function () {
+ var container = UI.get('geometryPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TorusKnotBufferGeometry) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var radius = UI.get('radius', this.id);
+ var tube = UI.get('tube', this.id);
+ var tubularSegments = UI.get('tubularSegments', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var p = UI.get('p', this.id);
+ var q = UI.get('q', this.id);
+
+ radius.setValue(this.selected.geometry.parameters.radius);
+ tube.setValue(this.selected.geometry.parameters.tube);
+ tubularSegments.setValue(this.selected.geometry.parameters.tubularSegments);
+ radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
+ p.setValue(this.selected.geometry.parameters.p);
+ q.setValue(this.selected.geometry.parameters.q);
+};
+
+TorusKnotGeometryComponent.prototype.onChangeGeometry = function () {
+ var radius = UI.get('radius', this.id);
+ var tube = UI.get('tube', this.id);
+ var tubularSegments = UI.get('tubularSegments', this.id);
+ var radialSegments = UI.get('radialSegments', this.id);
+ var p = UI.get('p', this.id);
+ var q = UI.get('q', this.id);
+
+ this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.TorusKnotBufferGeometry(
+ radius.getValue(),
+ tube.getValue(),
+ tubularSegments.getValue(),
+ radialSegments.getValue(),
+ p.getValue(),
+ q.getValue()
+ )));
+};
+
+export default TorusKnotGeometryComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/object/PerlinTerrainComponent.js b/ShadowEditor.Core/src/component/object/PerlinTerrainComponent.js
new file mode 100644
index 00000000..44669cea
--- /dev/null
+++ b/ShadowEditor.Core/src/component/object/PerlinTerrainComponent.js
@@ -0,0 +1,175 @@
+import BaseComponent from '../BaseComponent';
+import PerlinTerrain from '../../object/terrain/PerlinTerrain';
+
+/**
+ * 柏林地形组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function PerlinTerrainComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+PerlinTerrainComponent.prototype = Object.create(BaseComponent.prototype);
+PerlinTerrainComponent.prototype.constructor = PerlinTerrainComponent;
+
+PerlinTerrainComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'perlinPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '柏林地形'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'int',
+ id: 'width',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 1000,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度'
+ }, {
+ xtype: 'int',
+ id: 'depth',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 1000,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度分段'
+ }, {
+ xtype: 'int',
+ id: 'widthSegments',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 256,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度分段'
+ }, {
+ xtype: 'int',
+ id: 'depthSegments',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 256,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '质量'
+ }, {
+ xtype: 'int',
+ id: 'quality',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 80,
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+PerlinTerrainComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+PerlinTerrainComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+PerlinTerrainComponent.prototype.updateUI = function () {
+ var container = UI.get('perlinPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof PerlinTerrain) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var width = UI.get('width', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+ var quality = UI.get('quality', this.id);
+
+ width.setValue(this.selected.userData.width);
+ depth.setValue(this.selected.userData.depth);
+ widthSegments.setValue(this.selected.userData.widthSegments);
+ depthSegments.setValue(this.selected.userData.depthSegments);
+ quality.setValue(this.selected.userData.quality);
+};
+
+PerlinTerrainComponent.prototype.onChange = function () {
+ var width = UI.get('width', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+ var quality = UI.get('quality', this.id);
+
+ var terrain = new PerlinTerrain(
+ width.getValue(),
+ depth.getValue(),
+ widthSegments.getValue(),
+ depthSegments.getValue(),
+ quality.getValue()
+ );
+
+ var editor = this.app.editor;
+
+ var index = editor.scene.children.indexOf(this.selected);
+ if (index > -1) {
+ editor.scene.children[index] = terrain;
+ terrain.parent = this.selected.parent;
+ this.selected.parent = null;
+ this.app.call(`objectRemoved`, this, this.selected);
+ this.app.call(`objectAdded`, this, terrain);
+ editor.select(terrain);
+ this.app.call('sceneGraphChanged', this.id);
+ }
+};
+
+export default PerlinTerrainComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/object/ShaderTerrainComponent.js b/ShadowEditor.Core/src/component/object/ShaderTerrainComponent.js
new file mode 100644
index 00000000..e69de29b
diff --git a/ShadowEditor.Core/src/component/object/SkyComponent.js b/ShadowEditor.Core/src/component/object/SkyComponent.js
new file mode 100644
index 00000000..84c6a5be
--- /dev/null
+++ b/ShadowEditor.Core/src/component/object/SkyComponent.js
@@ -0,0 +1,174 @@
+import BaseComponent from '../BaseComponent';
+import Sky from '../../object/component/Sky';
+
+/**
+ * 天空组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SkyComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+SkyComponent.prototype = Object.create(BaseComponent.prototype);
+SkyComponent.prototype.constructor = SkyComponent;
+
+SkyComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'skyPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '天空'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '浑浊度'
+ }, {
+ xtype: 'number',
+ id: 'turbidity',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 10,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '瑞利'
+ }, {
+ xtype: 'number',
+ id: 'rayleigh',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 2,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '亮度'
+ }, {
+ xtype: 'number',
+ id: 'luminance',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 1,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: 'Mie系数'
+ }, {
+ xtype: 'number',
+ id: 'mieCoefficient',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 0.005,
+ unit: '%',
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: 'Mie方向'
+ }, {
+ xtype: 'number',
+ id: 'mieDirectionalG',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 0.005,
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+SkyComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+SkyComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+SkyComponent.prototype.updateUI = function () {
+ var container = UI.get('skyPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof Sky) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var turbidity = UI.get('turbidity', this.id);
+ var rayleigh = UI.get('rayleigh', this.id);
+ var luminance = UI.get('luminance', this.id);
+ var mieCoefficient = UI.get('mieCoefficient', this.id);
+ var mieDirectionalG = UI.get('mieDirectionalG', this.id);
+
+ turbidity.setValue(this.selected.userData.turbidity);
+ rayleigh.setValue(this.selected.userData.rayleigh);
+ luminance.setValue(this.selected.userData.luminance);
+ mieCoefficient.setValue(this.selected.userData.mieCoefficient * 100);
+ mieDirectionalG.setValue(this.selected.userData.mieDirectionalG);
+};
+
+SkyComponent.prototype.onChange = function () {
+ var turbidity = UI.get('turbidity', this.id);
+ var rayleigh = UI.get('rayleigh', this.id);
+ var luminance = UI.get('luminance', this.id);
+ var mieCoefficient = UI.get('mieCoefficient', this.id);
+ var mieDirectionalG = UI.get('mieDirectionalG', this.id);
+
+ this.selected.userData.turbidity = turbidity.getValue();
+ this.selected.userData.rayleigh = rayleigh.getValue();
+ this.selected.userData.luminance = luminance.getValue();
+ this.selected.userData.mieCoefficient = mieCoefficient.getValue() / 100;
+ this.selected.userData.mieDirectionalG = mieDirectionalG.getValue();
+
+ var sky = this.selected.children.filter(n => n instanceof THREE.Sky)[0];
+ if (sky) {
+ var uniforms = sky.material.uniforms;
+ uniforms.turbidity.value = turbidity.getValue();
+ uniforms.rayleigh.value = rayleigh.getValue();
+ uniforms.luminance.value = luminance.getValue();
+ uniforms.mieCoefficient.value = mieCoefficient.getValue() / 100;
+ uniforms.mieDirectionalG.value = mieDirectionalG.getValue();
+ sky.material.needsUpdate = true;
+ }
+
+ this.app.call(`objectSelected`, this, this.selected);
+};
+
+export default SkyComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/physics/PhysicsWorldComponent.js b/ShadowEditor.Core/src/component/physics/PhysicsWorldComponent.js
new file mode 100644
index 00000000..fde8589b
--- /dev/null
+++ b/ShadowEditor.Core/src/component/physics/PhysicsWorldComponent.js
@@ -0,0 +1,140 @@
+import BaseComponent from '../BaseComponent';
+
+/**
+ * 基本信息组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function PhysicsWorldComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+PhysicsWorldComponent.prototype = Object.create(BaseComponent.prototype);
+PhysicsWorldComponent.prototype.constructor = PhysicsWorldComponent;
+
+PhysicsWorldComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'objectPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '物理环境'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '碰撞配置'
+ }, {
+ xtype: 'select',
+ id: 'configType',
+ scope: this.id,
+ options: {
+ 'btDefaultCollisionConfiguration': '默认碰撞配置', // 无法使用布料
+ 'btSoftBodyRigidBodyCollisionConfiguration': '软体刚体碰撞配置'
+ },
+ onChange: this.onChangeConfigType.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '重力常数'
+ }, {
+ xtype: 'number',
+ id: 'gravityConstantX',
+ scope: this.id,
+ onChange: this.onChangeGravityConstant.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'gravityConstantY',
+ scope: this.id,
+ onChange: this.onChangeGravityConstant.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'gravityConstantZ',
+ scope: this.id,
+ onChange: this.onChangeGravityConstant.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+PhysicsWorldComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+PhysicsWorldComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+PhysicsWorldComponent.prototype.updateUI = function () {
+ var container = UI.get('objectPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Scene) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = app.physics;
+
+ var configType = UI.get('configType', this.id);
+ var gravityConstantX = UI.get('gravityConstantX', this.id);
+ var gravityConstantY = UI.get('gravityConstantY', this.id);
+ var gravityConstantZ = UI.get('gravityConstantZ', this.id);
+
+ if (this.selected.collisionConfiguration instanceof Ammo.btSoftBodyRigidBodyCollisionConfiguration) {
+ configType.setValue('btSoftBodyRigidBodyCollisionConfiguration');
+ } else {
+ configType.setValue('btDefaultCollisionConfiguration');
+ }
+
+ var gravity = this.selected.world.getGravity();
+ gravityConstantX.setValue(gravity.x());
+ gravityConstantY.setValue(gravity.y());
+ gravityConstantZ.setValue(gravity.z());
+};
+
+PhysicsWorldComponent.prototype.onChangeConfigType = function () {
+ var configType = UI.get('configType', this.id);
+ if (configType === 'btSoftBodyRigidBodyCollisionConfiguration') {
+ this.selected.collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
+ } else {
+ this.selected.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
+ }
+ this.selected.dispatcher = new Ammo.btCollisionDispatcher(this.selected.collisionConfiguration);
+ this.selected.world.dispatcher = this.selected.dispatcher;
+};
+
+PhysicsWorldComponent.prototype.onChangeGravityConstant = function () {
+ var gravityConstantX = UI.get('gravityConstantX', this.id);
+ var gravityConstantY = UI.get('gravityConstantY', this.id);
+ var gravityConstantZ = UI.get('gravityConstantZ', this.id);
+
+ var gravity = new Ammo.btVector3(gravityConstantX.getValue(), gravityConstantY.getValue(), gravityConstantZ.getValue());
+ this.selected.world.setGravity(gravity);
+ this.selected.world.getWorldInfo().set_m_gravity(gravity);
+};
+
+export default PhysicsWorldComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/physics/RigidBodyComponent.js b/ShadowEditor.Core/src/component/physics/RigidBodyComponent.js
new file mode 100644
index 00000000..7589e6d4
--- /dev/null
+++ b/ShadowEditor.Core/src/component/physics/RigidBodyComponent.js
@@ -0,0 +1,159 @@
+import BaseComponent from '../BaseComponent';
+
+/**
+ * 刚体组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function RigidBodyComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+RigidBodyComponent.prototype = Object.create(BaseComponent.prototype);
+RigidBodyComponent.prototype.constructor = RigidBodyComponent;
+
+RigidBodyComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ id: 'rigidBodyPanel',
+ scope: this.id,
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '刚体'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '形状'
+ }, {
+ xtype: 'input',
+ id: 'shape',
+ scope: this.id,
+ disabled: true,
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '质量'
+ }, {
+ xtype: 'number',
+ id: 'mass',
+ scope: this.id,
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '惯性'
+ }, {
+ xtype: 'number',
+ id: 'inertiaX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'inertiaY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'inertiaZ',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+RigidBodyComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+RigidBodyComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+RigidBodyComponent.prototype.updateUI = function () {
+ var container = UI.get('rigidBodyPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.userData.physics) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var shape = UI.get('shape', this.id);
+ var mass = UI.get('mass', this.id);
+ var inertiaX = UI.get('inertiaX', this.id);
+ var inertiaY = UI.get('inertiaY', this.id);
+ var inertiaZ = UI.get('inertiaZ', this.id);
+
+ var physics = this.selected.userData.physics;
+
+ shape.setValue(physics.shape);
+ mass.setValue(physics.mass);
+ inertiaX.setValue(physics.inertia.x);
+ inertiaY.setValue(physics.inertia.y);
+ inertiaZ.setValue(physics.inertia.z);
+};
+
+RigidBodyComponent.prototype.onChange = function () {
+ var shape = UI.get('shape', this.id);
+ var mass = UI.get('mass', this.id);
+ var inertiaX = UI.get('inertiaX', this.id);
+ var inertiaY = UI.get('inertiaY', this.id);
+ var inertiaZ = UI.get('inertiaZ', this.id);
+
+ var physics = this.selected.userData.physics;
+
+ physics.shape = shape.getValue();
+ physics.mass = mass.getValue();
+ physics.inertia.x = inertiaX.getValue();
+ physics.inertia.y = inertiaY.getValue();
+ physics.inertia.z = inertiaZ.getValue();
+};
+
+export default RigidBodyComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/shader/raw_shader_material_fragment.glsl b/ShadowEditor.Core/src/component/shader/raw_shader_material_fragment.glsl
new file mode 100644
index 00000000..47e22776
--- /dev/null
+++ b/ShadowEditor.Core/src/component/shader/raw_shader_material_fragment.glsl
@@ -0,0 +1,5 @@
+precision mediump float;
+
+void main() {
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/shader/raw_shader_material_vertex.glsl b/ShadowEditor.Core/src/component/shader/raw_shader_material_vertex.glsl
new file mode 100644
index 00000000..b6c7eedc
--- /dev/null
+++ b/ShadowEditor.Core/src/component/shader/raw_shader_material_vertex.glsl
@@ -0,0 +1,10 @@
+precision mediump float;
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+attribute vec3 position;
+
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/shader/shader_material_fragment.glsl b/ShadowEditor.Core/src/component/shader/shader_material_fragment.glsl
new file mode 100644
index 00000000..ab7e4dd5
--- /dev/null
+++ b/ShadowEditor.Core/src/component/shader/shader_material_fragment.glsl
@@ -0,0 +1,3 @@
+void main() {
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/shader/shader_material_vertex.glsl b/ShadowEditor.Core/src/component/shader/shader_material_vertex.glsl
new file mode 100644
index 00000000..fef566cf
--- /dev/null
+++ b/ShadowEditor.Core/src/component/shader/shader_material_vertex.glsl
@@ -0,0 +1,3 @@
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/component/water/WaterComponent.js b/ShadowEditor.Core/src/component/water/WaterComponent.js
new file mode 100644
index 00000000..0bd77b03
--- /dev/null
+++ b/ShadowEditor.Core/src/component/water/WaterComponent.js
@@ -0,0 +1,175 @@
+import BaseComponent from '../BaseComponent';
+import PerlinTerrain from '../../object/terrain/PerlinTerrain';
+
+/**
+ * 水组件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function WaterComponent(options) {
+ BaseComponent.call(this, options);
+ this.selected = null;
+}
+
+WaterComponent.prototype = Object.create(BaseComponent.prototype);
+WaterComponent.prototype.constructor = WaterComponent;
+
+WaterComponent.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'perlinPanel',
+ scope: this.id,
+ cls: 'Panel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ width: '100%',
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '柏林地形'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度'
+ }, {
+ xtype: 'int',
+ id: 'width',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 1000,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度'
+ }, {
+ xtype: 'int',
+ id: 'depth',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 1000,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '宽度分段'
+ }, {
+ xtype: 'int',
+ id: 'widthSegments',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 256,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '深度分段'
+ }, {
+ xtype: 'int',
+ id: 'depthSegments',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 256,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '质量'
+ }, {
+ xtype: 'int',
+ id: 'quality',
+ scope: this.id,
+ range: [0, Infinity],
+ value: 80,
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+};
+
+WaterComponent.prototype.onObjectSelected = function () {
+ this.updateUI();
+};
+
+WaterComponent.prototype.onObjectChanged = function () {
+ this.updateUI();
+};
+
+WaterComponent.prototype.updateUI = function () {
+ var container = UI.get('perlinPanel', this.id);
+ var editor = this.app.editor;
+ if (editor.selected && editor.selected instanceof PerlinTerrain) {
+ container.dom.style.display = '';
+ } else {
+ container.dom.style.display = 'none';
+ return;
+ }
+
+ this.selected = editor.selected;
+
+ var width = UI.get('width', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+ var quality = UI.get('quality', this.id);
+
+ width.setValue(this.selected.userData.width);
+ depth.setValue(this.selected.userData.depth);
+ widthSegments.setValue(this.selected.userData.widthSegments);
+ depthSegments.setValue(this.selected.userData.depthSegments);
+ quality.setValue(this.selected.userData.quality);
+};
+
+WaterComponent.prototype.onChange = function () {
+ var width = UI.get('width', this.id);
+ var depth = UI.get('depth', this.id);
+ var widthSegments = UI.get('widthSegments', this.id);
+ var depthSegments = UI.get('depthSegments', this.id);
+ var quality = UI.get('quality', this.id);
+
+ var terrain = new PerlinTerrain(
+ width.getValue(),
+ depth.getValue(),
+ widthSegments.getValue(),
+ depthSegments.getValue(),
+ quality.getValue()
+ );
+
+ var editor = this.app.editor;
+
+ var index = editor.scene.children.indexOf(this.selected);
+ if (index > -1) {
+ editor.scene.children[index] = terrain;
+ terrain.parent = this.selected.parent;
+ this.selected.parent = null;
+ this.app.call(`objectRemoved`, this, this.selected);
+ this.app.call(`objectAdded`, this, terrain);
+ editor.select(terrain);
+ this.app.call('sceneGraphChanged', this.id);
+ }
+};
+
+export default WaterComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/History.js b/ShadowEditor.Core/src/core/History.js
new file mode 100644
index 00000000..63dd11bb
--- /dev/null
+++ b/ShadowEditor.Core/src/core/History.js
@@ -0,0 +1,213 @@
+import Command from '../command/Command';
+import { UI } from '../third_party';
+
+/**
+ * 历史记录
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+function History(editor) {
+ this.app = editor.app;
+
+ this.editor = editor;
+ this.undos = [];
+ this.redos = [];
+ this.lastCmdTime = new Date();
+ this.idCounter = 0;
+
+ Command.call(this, editor);
+};
+
+History.prototype = Object.create(Command.prototype);
+
+Object.assign(History.prototype, {
+
+ constructor: History,
+
+ execute: function (cmd, optionalName) {
+
+ var lastCmd = this.undos[this.undos.length - 1];
+ var timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
+
+ var isUpdatableCmd = lastCmd &&
+ lastCmd.updatable &&
+ cmd.updatable &&
+ lastCmd.object === cmd.object &&
+ lastCmd.type === cmd.type &&
+ lastCmd.script === cmd.script &&
+ lastCmd.attributeName === cmd.attributeName;
+
+ if (isUpdatableCmd && cmd.type === "SetScriptValueCommand") {
+
+ // When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored
+
+ lastCmd.update(cmd);
+ cmd = lastCmd;
+
+ } else if (isUpdatableCmd && timeDifference < 500) {
+
+ lastCmd.update(cmd);
+ cmd = lastCmd;
+
+ } else {
+
+ // the command is not updatable and is added as a new part of the history
+
+ this.undos.push(cmd);
+ cmd.id = ++this.idCounter;
+
+ }
+ cmd.name = (optionalName !== undefined) ? optionalName : cmd.name;
+ cmd.execute();
+ cmd.inMemory = true;
+
+ this.lastCmdTime = new Date();
+
+ // clearing all the redo-commands
+
+ this.redos = [];
+ this.app.call('historyChanged', this, cmd);
+
+ },
+
+ undo: function () {
+ var cmd = undefined;
+
+ if (this.undos.length > 0) {
+ cmd = this.undos.pop();
+
+ if (cmd.inMemory === false) {
+ cmd.fromJSON(cmd.json);
+ }
+ }
+
+ if (cmd !== undefined) {
+ cmd.undo();
+ this.redos.push(cmd);
+ this.app.call('historyChanged', this, cmd);
+ }
+
+ return cmd;
+ },
+
+ redo: function () {
+ var cmd = undefined;
+
+ if (this.redos.length > 0) {
+ cmd = this.redos.pop();
+
+ if (cmd.inMemory === false) {
+ cmd.fromJSON(cmd.json);
+ }
+ }
+
+ if (cmd !== undefined) {
+ cmd.execute();
+ this.undos.push(cmd);
+ this.app.call('historyChanged', this, cmd);
+ }
+
+ return cmd;
+ },
+
+ toJSON: function () {
+ var history = {};
+ history.undos = [];
+ history.redos = [];
+
+ // Append Undos to History
+ for (var i = 0; i < this.undos.length; i++) {
+ if (this.undos[i].hasOwnProperty("json")) {
+ history.undos.push(this.undos[i].json);
+ }
+ }
+
+ // Append Redos to History
+ for (var i = 0; i < this.redos.length; i++) {
+ if (this.redos[i].hasOwnProperty("json")) {
+ history.redos.push(this.redos[i].json);
+ }
+ }
+
+ return history;
+ },
+
+ fromJSON: function (json) {
+ if (json === undefined) return;
+
+ for (var i = 0; i < json.undos.length; i++) {
+ var cmdJSON = json.undos[i];
+ var cmd = new window[cmdJSON.type](); // creates a new object of type "json.type"
+ cmd.json = cmdJSON;
+ cmd.id = cmdJSON.id;
+ cmd.name = cmdJSON.name;
+ this.undos.push(cmd);
+ this.idCounter = (cmdJSON.id > this.idCounter) ? cmdJSON.id : this.idCounter; // set last used idCounter
+ }
+
+ for (var i = 0; i < json.redos.length; i++) {
+ var cmdJSON = json.redos[i];
+ var cmd = new window[cmdJSON.type](); // creates a new object of type "json.type"
+ cmd.json = cmdJSON;
+ cmd.id = cmdJSON.id;
+ cmd.name = cmdJSON.name;
+ this.redos.push(cmd);
+ this.idCounter = (cmdJSON.id > this.idCounter) ? cmdJSON.id : this.idCounter; // set last used idCounter
+ }
+
+ // Select the last executed undo-command
+ this.app.call('historyChanged', this, this.undos[this.undos.length - 1]);
+ },
+
+ clear: function () {
+ this.undos = [];
+ this.redos = [];
+ this.idCounter = 0;
+
+ this.app.call('historyChanged', this);
+ },
+
+ goToState: function (id) {
+ var cmd = this.undos.length > 0 ? this.undos[this.undos.length - 1] : undefined; // next cmd to pop
+
+ if (cmd === undefined || id > cmd.id) {
+ cmd = this.redo();
+ while (cmd !== undefined && id > cmd.id) {
+ cmd = this.redo();
+ }
+ } else {
+ while (true) {
+ cmd = this.undos[this.undos.length - 1]; // next cmd to pop
+ if (cmd === undefined || id === cmd.id) break;
+ cmd = this.undo();
+ }
+ }
+
+ this.editor.app.call('sceneGraphChanged', this);
+ this.editor.app.call('historyChanged', this, cmd);
+ },
+
+ enableSerialization: function (id) {
+
+ /**
+ * because there might be commands in this.undos and this.redos
+ * which have not been serialized with .toJSON() we go back
+ * to the oldest command and redo one command after the other
+ * while also calling .toJSON() on them.
+ */
+
+ this.goToState(-1);
+
+ var cmd = this.redo();
+ while (cmd !== undefined) {
+ if (!cmd.hasOwnProperty("json")) {
+ cmd.json = cmd.toJSON();
+ }
+ cmd = this.redo();
+ }
+
+ this.goToState(id);
+ }
+});
+
+export default History;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/ServerModelUserData.js b/ShadowEditor.Core/src/core/ServerModelUserData.js
new file mode 100644
index 00000000..09bbda56
--- /dev/null
+++ b/ShadowEditor.Core/src/core/ServerModelUserData.js
@@ -0,0 +1,21 @@
+/**
+ * 模型对象userData字段保存数据模板
+ * @author tengge / https://github.com/tengge1
+ */
+function UserData() {
+ this.ID = ''; // 模型ID,例如:5b7e38653fb63b0b50d332eb
+
+ this.Name = ''; // 模型名称
+ this.TotalPinYin = ''; // 模型名称全拼
+ this.FirstPinYin = ''; // 模型名称拼音首字母
+
+ this.Type = ''; // 模型类型
+ this.Url = ''; // 模型文件url
+ this.Thumbnail = ''; // 模型缩略图
+
+ this.Server = true; // 是否从服务端加载
+
+ this.CustomData = {}; // 自定义数据
+}
+
+export default UserData;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/Storage.js b/ShadowEditor.Core/src/core/Storage.js
new file mode 100644
index 00000000..2ac922b7
--- /dev/null
+++ b/ShadowEditor.Core/src/core/Storage.js
@@ -0,0 +1,75 @@
+/**
+ * 本地存储
+ * @author mrdoob / http://mrdoob.com/
+ */
+function Storage() {
+ var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
+
+ if (indexedDB === undefined) {
+ console.warn('Storage: IndexedDB不可用。');
+ return { init: function () { }, get: function () { }, set: function () { }, clear: function () { } };
+ }
+
+ var name = 'threejs-editor';
+ var version = 1;
+
+ var database;
+
+ return {
+ init: function (callback) {
+ var request = indexedDB.open(name, version);
+ request.onupgradeneeded = function (event) {
+ var db = event.target.result;
+
+ if (db.objectStoreNames.contains('states') === false) {
+
+ db.createObjectStore('states');
+
+ }
+ };
+ request.onsuccess = function (event) {
+ database = event.target.result;
+
+ callback();
+ };
+ request.onerror = function (event) {
+ console.error('IndexedDB', event);
+ };
+ },
+
+ get: function (callback) {
+ var transaction = database.transaction(['states'], 'readwrite');
+ var objectStore = transaction.objectStore('states');
+ var request = objectStore.get(0);
+ request.onsuccess = function (event) {
+ callback(event.target.result);
+ };
+
+ },
+
+ set: function (data, callback) {
+ var start = performance.now();
+
+ var transaction = database.transaction(['states'], 'readwrite');
+ var objectStore = transaction.objectStore('states');
+ var request = objectStore.put(data, 0);
+ request.onsuccess = function (event) {
+ console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', '保存到IndexedDB中。 ' + (performance.now() - start).toFixed(2) + 'ms');
+ };
+
+ },
+
+ clear: function () {
+ if (database === undefined) return;
+
+ var transaction = database.transaction(['states'], 'readwrite');
+ var objectStore = transaction.objectStore('states');
+ var request = objectStore.clear();
+ request.onsuccess = function (event) {
+ console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', '清空IndexedDB。');
+ };
+ }
+ };
+};
+
+export default Storage;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/UserData.js b/ShadowEditor.Core/src/core/UserData.js
new file mode 100644
index 00000000..cc7926a0
--- /dev/null
+++ b/ShadowEditor.Core/src/core/UserData.js
@@ -0,0 +1,9 @@
+/**
+ * 场景对象自定义数据类型
+ * @author tengge / https://github.com/tengge1
+ */
+function UserData() {
+ type: 'obj'
+}
+
+export default UserData;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/userData/AudioUserDataItem.js b/ShadowEditor.Core/src/core/userData/AudioUserDataItem.js
new file mode 100644
index 00000000..ba6d8bcb
--- /dev/null
+++ b/ShadowEditor.Core/src/core/userData/AudioUserDataItem.js
@@ -0,0 +1,14 @@
+import UserDataItemBase from './UserDataItemBase';
+
+function AudioUserDataItem(options) {
+ UserDataItemBase.call(this, options);
+
+ this.type = 'audio';
+ this.name = options.name || '(未知)';
+ this.url = options.url || '';
+};
+
+AudioUserDataItem.prototype = Object.create(UserDataItemBase.prototype);
+AudioUserDataItem.prototype.constructor = AudioUserDataItem;
+
+export default AudioUserDataItem;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/core/userData/UserDataItemBase.js b/ShadowEditor.Core/src/core/userData/UserDataItemBase.js
new file mode 100644
index 00000000..03bafa3e
--- /dev/null
+++ b/ShadowEditor.Core/src/core/userData/UserDataItemBase.js
@@ -0,0 +1,8 @@
+/**
+ * 用户自定义数据部件基类
+ */
+function UserDataItemBase(options) {
+ this.type = options.type || 'unknown';
+}
+
+export default UserDataItemBase;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/Editor.js b/ShadowEditor.Core/src/editor/Editor.js
new file mode 100644
index 00000000..063518fc
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/Editor.js
@@ -0,0 +1,396 @@
+import History from '../core/History';
+import Storage from '../core/Storage';
+import AnimationManager from '../animation/AnimationManager';
+
+/**
+ * 编辑器
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Editor(app) {
+ this.app = app;
+ this.app.editor = this;
+
+ // 基础
+ this.history = new History(this);
+ this.storage = new Storage();
+
+ // 场景
+ this.scene = new THREE.Scene();
+ this.scene.name = '场景';
+ this.scene.background = new THREE.Color(0xaaaaaa);
+
+ this.sceneHelpers = new THREE.Scene();
+
+ this.sceneID = null; // 当前场景ID
+ this.sceneName = null; // 当前场景名称
+
+ // 相机
+ this.DEFAULT_CAMERA = new THREE.PerspectiveCamera(50, 1, 0.1, 10000);
+ this.DEFAULT_CAMERA.name = '默认相机';
+ this.DEFAULT_CAMERA.userData.isDefault = true;
+ this.DEFAULT_CAMERA.position.set(20, 10, 20);
+ this.DEFAULT_CAMERA.lookAt(new THREE.Vector3());
+
+ this.camera = this.DEFAULT_CAMERA.clone();
+
+ // 渲染器
+ this.renderer = new THREE.WebGLRenderer({
+ antialias: true
+ });
+ this.renderer.gammaInput = false;
+ this.renderer.gammaOutput = false;
+ this.renderer.shadowMap.enabled = true;
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+ this.renderer.autoClear = false;
+ this.renderer.autoUpdateScene = false;
+ this.renderer.setPixelRatio(window.devicePixelRatio);
+
+ this.app.viewport.container.dom.appendChild(this.renderer.domElement);
+ this.renderer.setSize(this.app.viewport.container.dom.offsetWidth, this.app.viewport.container.dom.offsetHeight);
+
+ // 音频监听器
+ this.audioListener = new THREE.AudioListener();
+ this.audioListener.name = '音频监听器';
+
+ // 物体
+ this.object = {};
+
+ // 物体
+ this.objects = [];
+
+ // 脚本 格式:{ uuid: { id: 'mongoDB id', name: 'Script Name', type: 'Script Type', source: 'Source Code', uuid: 'uuid' }}
+ // 其中,uuid是创建脚本时自动生成,不可改变,关联时使用,id是mongo数据库ID字段;name:随便填写;type:javascript,vertexShader, fragmentShader, json;source:源码。
+ this.scripts = {};
+ this.animation = new AnimationManager(this.app);
+
+ // 帮助器
+ this.helpers = {};
+
+ // 当前选中物体
+ this.selected = null;
+
+ // 网格
+ this.grid = new THREE.GridHelper(30, 30, 0x444444, 0x888888);
+ this.grid.visible = this.app.options.showGrid;
+ this.sceneHelpers.add(this.grid);
+
+ // 平移旋转缩放控件
+ this.transformControls = new THREE.TransformControls(this.camera, this.app.viewport.container.dom);
+ this.sceneHelpers.add(this.transformControls);
+
+ // 编辑器控件
+ this.controls = new THREE.EditorControls(this.camera, this.app.viewport.container.dom);
+
+ // 性能控件
+ this.stats = new Stats();
+ this.stats.dom.style.position = 'absolute';
+ this.stats.dom.style.left = '8px';
+ this.stats.dom.style.top = '8px';
+ this.stats.dom.style.zIndex = 'initial';
+ this.app.viewport.container.dom.appendChild(this.stats.dom);
+
+ // 事件
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+ this.app.on(`optionsChanged.${this.id}`, this.onOptionsChanged.bind(this));
+};
+
+Editor.prototype.onAppStarted = function () {
+ this.clear();
+};
+
+// -------------------- 场景 --------------------------
+
+Editor.prototype.setScene = function (scene) { // 设置场景
+ // 移除原有物体
+ var objects = this.scene.children;
+ while (objects.length > 0) {
+ this.removeObject(objects[0]);
+ }
+
+ // 添加新物体
+ var children = scene.children.slice();
+ scene.children.length = 0;
+ this.scene = scene;
+
+ children.forEach(n => {
+ this.addObject(n);
+ });
+
+ this.app.call('sceneGraphChanged', this);
+};
+
+Editor.prototype.clear = function (addObject = true) { // 清空场景
+ this.history.clear();
+ this.storage.clear();
+
+ this.camera.copy(this.DEFAULT_CAMERA);
+
+ if (this.camera.children.findIndex(o => o instanceof THREE.AudioListener) === -1) {
+ this.camera.add(this.audioListener);
+ }
+
+ if (this.scene.background instanceof THREE.Texture) {
+ this.scene.background = new THREE.Color(0xaaaaaa);
+ } else if (this.scene.background instanceof THREE.Color) {
+ this.scene.background.setHex(0xaaaaaa);
+ }
+
+ this.scene.fog = null;
+
+ var objects = this.scene.children;
+
+ while (objects.length > 0) {
+ this.removeObject(objects[0]);
+ }
+
+ this.textures = {};
+ this.scripts = {};
+ this.animation.clear();
+
+ this.deselect();
+
+ // 添加默认元素
+ if (addObject) {
+ var light1 = new THREE.AmbientLight(0xffffff, 0.24);
+ light1.name = '环境光';
+ this.addObject(light1);
+
+ var light2 = new THREE.DirectionalLight(0xffffff, 0.56);
+ light2.name = '平行光';
+ light2.castShadow = true;
+ light2.position.set(5, 10, 7.5);
+ light2.shadow.mapSize.x = 2048;
+ light2.shadow.mapSize.y = 2048;
+ light2.shadow.camera.left = -100;
+ light2.shadow.camera.right = 100;
+ light2.shadow.camera.top = 100;
+ light2.shadow.camera.bottom = -100;
+ this.addObject(light2);
+ }
+
+ this.app.call('editorCleared', this);
+ this.app.call('scriptChanged', this);
+ this.app.call('animationChanged', this);
+};
+
+// ---------------------- 物体 ---------------------------
+
+Editor.prototype.objectByUuid = function (uuid) { // 根据uuid获取物体
+ return this.scene.getObjectByProperty('uuid', uuid, true);
+};
+
+Editor.prototype.addObject = function (object) { // 添加物体
+ this.scene.add(object);
+
+ object.traverse(child => {
+ this.addHelper(child);
+ });
+
+ this.app.call('objectAdded', this, object);
+ this.app.call('sceneGraphChanged', this);
+};
+
+Editor.prototype.moveObject = function (object, parent, before) { // 移动物体
+ if (parent === undefined) {
+ parent = this.scene;
+ }
+
+ parent.add(object);
+
+ // sort children array
+ if (before !== undefined) {
+ var index = parent.children.indexOf(before);
+ parent.children.splice(index, 0, object);
+ parent.children.pop();
+ }
+
+ this.app.call('sceneGraphChanged', this);
+};
+
+Editor.prototype.removeObject = function (object) { // 移除物体
+ if (object.parent === null) { // 避免删除相机或场景
+ return;
+ }
+
+ object.traverse(child => {
+ this.removeHelper(child);
+ });
+
+ object.parent.remove(object);
+
+ this.app.call('objectRemoved', this, object);
+ this.app.call('sceneGraphChanged', this);
+};
+
+// ------------------------- 帮助 ------------------------------
+
+Editor.prototype.addHelper = function (object) { // 添加物体帮助器
+ var options = this.app.options;
+
+ var helper = null;
+
+ if (object instanceof THREE.Camera) { // 相机
+ helper = new THREE.CameraHelper(object, 1);
+ helper.visible = options.showCameraHelper;
+ } else if (object instanceof THREE.PointLight) { // 点光源
+ helper = new THREE.PointLightHelper(object, 1);
+ helper.visible = options.showPointLightHelper;
+ } else if (object instanceof THREE.DirectionalLight) { // 平行光
+ helper = new THREE.DirectionalLightHelper(object, 1);
+ helper.visible = options.showDirectionalLightHelper;
+ } else if (object instanceof THREE.SpotLight) { // 聚光灯
+ helper = new THREE.SpotLightHelper(object, 1);
+ helper.visible = options.showSpotLightHelper;
+ } else if (object instanceof THREE.HemisphereLight) { // 半球光
+ helper = new THREE.HemisphereLightHelper(object, 1);
+ helper.visible = options.showHemisphereLightHelper;
+ } else if (object instanceof THREE.RectAreaLight) { // 矩形光
+ helper = new THREE.RectAreaLightHelper(object, 0xffffff);
+ helper.visible = options.showRectAreaLightHelper;
+ } else if (object instanceof THREE.SkinnedMesh) { // 骨骼
+ helper = new THREE.SkeletonHelper(object);
+ helper.visible = options.showSkeletonHelper;
+ } else {
+ // 该类型物体没有帮助器
+ return;
+ }
+
+ var geometry = new THREE.SphereBufferGeometry(2, 4, 2);
+ var material = new THREE.MeshBasicMaterial({
+ color: 0xff0000,
+ visible: false
+ });
+
+ var picker = new THREE.Mesh(geometry, material);
+ picker.name = 'picker';
+ picker.userData.object = object;
+ helper.add(picker);
+
+ this.sceneHelpers.add(helper);
+ this.helpers[object.id] = helper;
+ this.objects.push(picker);
+};
+
+Editor.prototype.removeHelper = function (object) { // 移除物体帮助
+ if (this.helpers[object.id] !== undefined) {
+
+ var helper = this.helpers[object.id];
+ helper.parent.remove(helper);
+ delete this.helpers[object.id];
+
+ var objects = this.objects;
+ objects.splice(objects.indexOf(helper.getObjectByName('picker')), 1);
+ }
+};
+
+Editor.prototype.onOptionsChanged = function (options) { // 帮助器改变事件
+ this.grid.visible = options.showGrid === undefined ? true : options.showGrid;
+ Object.keys(this.helpers).forEach(n => {
+ var helper = this.helpers[n];
+ if (helper instanceof THREE.CameraHelper) {
+ helper.visible = options.showCameraHelper;
+ } else if (helper instanceof THREE.PointLightHelper) {
+ helper.visible = options.showPointLightHelper;
+ } else if (helper instanceof THREE.DirectionalLightHelper) {
+ helper.visible = options.showDirectionalLightHelper;
+ } else if (helper instanceof THREE.SpotLightHelper) {
+ helper.visible = options.showSpotLightHelper;
+ } else if (helper instanceof THREE.HemisphereLightHelper) {
+ helper.visible = options.showHemisphereLightHelper;
+ } else if (helper instanceof THREE.RectAreaLightHelper) {
+ helper.visible = options.showRectAreaLightHelper;
+ } else if (helper instanceof THREE.SkeletonHelper) {
+ helper.visible = options.showSkeletonHelper;
+ }
+ });
+};
+
+// ------------------------ 脚本 ----------------------------
+
+Editor.prototype.addScript = function (object, script) { // 添加脚本
+ if (this.scripts[object.uuid] === undefined) {
+ this.scripts[object.uuid] = [];
+ }
+
+ this.scripts[object.uuid].push(script);
+
+ this.app.call('scriptAdded', this, script);
+};
+
+Editor.prototype.removeScript = function (object, script) { // 移除脚本
+ if (this.scripts[object.uuid] === undefined) {
+ return;
+ }
+
+ var index = this.scripts[object.uuid].indexOf(script);
+
+ if (index !== -1) {
+ this.scripts[object.uuid].splice(index, 1);
+ }
+
+ this.app.call('scriptRemoved', this);
+};
+
+// ------------------------ 选中事件 --------------------------------
+
+Editor.prototype.select = function (object) { // 选中物体
+ if (this.selected === object) {
+ return;
+ }
+
+ this.selected = object;
+
+ this.app.call('objectSelected', this, object);
+};
+
+Editor.prototype.selectById = function (id) { // 根据id选中物体
+ if (id === this.camera.id) {
+ this.select(this.camera);
+ return;
+ }
+
+ this.select(this.scene.getObjectById(id, true));
+};
+
+Editor.prototype.selectByUuid = function (uuid) { // 根据uuid选中物体
+ var _this = this;
+ this.scene.traverse(function (child) {
+ if (child.uuid === uuid) {
+ _this.select(child);
+ }
+ });
+};
+
+Editor.prototype.deselect = function () { // 取消选中物体
+ this.select(null);
+};
+
+// ---------------------- 焦点事件 --------------------------
+
+Editor.prototype.focus = function (object) { // 设置焦点
+ this.app.call('objectFocused', this, object);
+};
+
+Editor.prototype.focusById = function (id) { // 根据id设置交点
+ var obj = this.scene.getObjectById(id, true);
+ if (obj) {
+ this.focus(obj);
+ }
+};
+
+// ----------------------- 命令事件 --------------------------
+
+Editor.prototype.execute = function (cmd, optionalName) { // 执行事件
+ this.history.execute(cmd, optionalName);
+};
+
+Editor.prototype.undo = function () { // 撤销事件
+ this.history.undo();
+};
+
+Editor.prototype.redo = function () { // 重做事件
+ this.history.redo();
+};
+
+export default Editor;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/Physics.js b/ShadowEditor.Core/src/editor/Physics.js
new file mode 100644
index 00000000..2983892c
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/Physics.js
@@ -0,0 +1,171 @@
+/**
+ * 物理
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function Physics(options) {
+ this.app = options.app;
+ this.app.physics = this;
+
+ // 各种参数
+ var gravityConstant = -9.8;
+ this.margin = 0.05;
+ this.transformAux1 = new Ammo.btTransform();
+
+ this.time = 0;
+ this.armMovement = 0;
+
+ // 物理环境配置
+ this.collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); // 软体刚体碰撞配置
+ this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration); // 碰撞调度器
+ this.broadphase = new Ammo.btDbvtBroadphase(); // dbvt粗测
+ this.solver = new Ammo.btSequentialImpulseConstraintSolver(); // 顺序脉冲约束求解器
+ this.softBodySolver = new Ammo.btDefaultSoftBodySolver(); // 默认软体求解器
+
+ this.world = new Ammo.btSoftRigidDynamicsWorld(this.dispatcher, this.broadphase, this.solver, this.collisionConfiguration, this.softBodySolver);
+
+ var gravity = new Ammo.btVector3(0, gravityConstant, 0);
+ this.world.setGravity(gravity);
+ this.world.getWorldInfo().set_m_gravity(gravity);
+
+ this.rigidBodies = [];
+
+ // 扔球
+ this.enableThrowBall = false;
+}
+
+Physics.prototype.start = function () {
+ this.app.on(`animate.Physics`, this.update.bind(this));
+ this.app.on(`mThrowBall.Physics`, this.onEnableThrowBall.bind(this));
+};
+
+/**
+ * 创建刚体
+ * @param {*} threeObject three.js中Object3D对象
+ * @param {*} physicsShape 物理形状
+ * @param {*} mass 重力
+ * @param {*} pos 位置
+ * @param {*} quat 旋转
+ */
+Physics.prototype.createRigidBody = function (threeObject, physicsShape, mass, pos, quat) {
+ threeObject.position.copy(pos);
+ threeObject.quaternion.copy(quat);
+
+ var transform = new Ammo.btTransform();
+ transform.setIdentity();
+ transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
+ transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
+ var motionState = new Ammo.btDefaultMotionState(transform);
+
+ // 惯性
+ var localInertia = new Ammo.btVector3(0, 0, 0);
+ physicsShape.calculateLocalInertia(mass, localInertia);
+
+ var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
+ var body = new Ammo.btRigidBody(rbInfo);
+
+ threeObject.userData.physicsBody = body;
+
+ // 重力大于0才响应物理事件
+ if (mass > 0) {
+ this.rigidBodies.push(threeObject);
+ body.setActivationState(4);
+ }
+
+ this.world.addRigidBody(body);
+
+ return body;
+};
+
+/**
+ * 创建一个平板
+ * @param {*} sx 长度
+ * @param {*} sy 厚度
+ * @param {*} sz 宽度
+ * @param {*} mass 重力
+ * @param {*} pos 位置
+ * @param {*} quat 旋转
+ * @param {*} material 材质
+ */
+Physics.prototype.createParalellepiped = function (sx, sy, sz, mass, pos, quat, material) {
+ var threeObject = new THREE.Mesh(new THREE.BoxBufferGeometry(sx, sy, sz, 1, 1, 1), material);
+ var shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
+ shape.setMargin(this.margin);
+
+ this.createRigidBody(threeObject, shape, mass, pos, quat);
+
+ return threeObject;
+};
+
+Physics.prototype.onThrowBall = function (event) {
+ var mouse = new THREE.Vector2();
+ var raycaster = new THREE.Raycaster();
+ var camera = this.app.editor.camera;
+
+ var width = UI.get('viewport').dom.clientWidth;
+ var height = UI.get('viewport').dom.clientHeight;
+
+ mouse.set((event.offsetX / width) * 2 - 1, -(event.offsetY / height) * 2 + 1);
+ raycaster.setFromCamera(mouse, camera);
+
+ // Creates a ball and throws it
+ var ballMass = 35;
+ var ballRadius = 0.4;
+ var ballMaterial = new THREE.MeshPhongMaterial({
+ color: 0x202020
+ });
+
+ var ball = new THREE.Mesh(new THREE.SphereBufferGeometry(ballRadius, 14, 10), ballMaterial);
+ ball.castShadow = true;
+ ball.receiveShadow = true;
+ this.app.editor.scene.add(ball);
+
+ var ballShape = new Ammo.btSphereShape(ballRadius);
+ ballShape.setMargin(this.margin);
+
+ var pos = new THREE.Vector3();
+ pos.copy(raycaster.ray.direction);
+ pos.add(raycaster.ray.origin);
+
+ var quat = new THREE.Quaternion();
+ quat.set(0, 0, 0, 1);
+
+ var ballBody = this.createRigidBody(ball, ballShape, ballMass, pos, quat);
+
+ pos.copy(raycaster.ray.direction);
+ pos.multiplyScalar(24);
+
+ ballBody.setLinearVelocity(new Ammo.btVector3(pos.x, pos.y, pos.z));
+};
+
+Physics.prototype.update = function (clock, deltaTime) {
+ this.world.stepSimulation(deltaTime, 10);
+
+ // Update rigid bodies
+ for (var i = 0, il = this.rigidBodies.length; i < il; i++) {
+ var objThree = this.rigidBodies[i];
+ var objPhys = objThree.userData.physicsBody;
+ var ms = objPhys.getMotionState();
+ if (ms) {
+ ms.getWorldTransform(this.transformAux1);
+ var p = this.transformAux1.getOrigin();
+ var q = this.transformAux1.getRotation();
+ objThree.position.set(p.x(), p.y(), p.z());
+ objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
+ }
+ }
+};
+
+Physics.prototype.onEnableThrowBall = function () {
+ if (this.enableThrowBall) {
+ this.enableThrowBall = false;
+ UI.get('mThrowBall').dom.innerHTML = '开启探测小球';
+ this.app.on(`click.Physics`, null);
+ } else {
+ this.enableThrowBall = true;
+ UI.get('mThrowBall').dom.innerHTML = '关闭探测小球';
+ this.app.on(`click.Physics`, this.onThrowBall.bind(this));
+ }
+};
+
+export default Physics;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/StatusBar.js b/ShadowEditor.Core/src/editor/StatusBar.js
new file mode 100644
index 00000000..281bfff0
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/StatusBar.js
@@ -0,0 +1,100 @@
+import { UI } from '../third_party';
+
+/**
+ * 状态栏
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function StatusBar(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+StatusBar.prototype = Object.create(UI.Control.prototype);
+StatusBar.prototype.constructor = StatusBar;
+
+StatusBar.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'statusBar',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '物体'
+ }, {
+ xtype: 'text',
+ id: 'objectsText',
+ scope: this.id,
+ text: '0' // 物体数
+ }, {
+ xtype: 'label',
+ text: '顶点'
+ }, {
+ xtype: 'text',
+ id: 'verticesText',
+ scope: this.id,
+ text: '0' // 顶点数
+ }, {
+ xtype: 'label',
+ text: '三角形'
+ }, {
+ xtype: 'text',
+ id: 'trianglesText',
+ scope: this.id,
+ text: '0' // 三角形数
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on('objectAdded.' + this.id, this.onUpdateInfo.bind(this));
+ this.app.on('objectRemoved.' + this.id, this.onUpdateInfo.bind(this));
+ this.app.on('geometryChanged.' + this.id, this.onUpdateInfo.bind(this));
+};
+
+StatusBar.prototype.onUpdateInfo = function () {
+ var editor = this.app.editor;
+
+ var scene = editor.scene;
+
+ var objects = 0, vertices = 0, triangles = 0;
+
+ for (var i = 0, l = scene.children.length; i < l; i++) {
+ var object = scene.children[i];
+
+ object.traverseVisible(function (object) {
+ objects++;
+
+ if (object instanceof THREE.Mesh) {
+ var geometry = object.geometry;
+
+ if (geometry instanceof THREE.Geometry) {
+ vertices += geometry.vertices.length;
+ triangles += geometry.faces.length;
+ } else if (geometry instanceof THREE.BufferGeometry) {
+ if (geometry.index !== null) {
+ vertices += geometry.index.count * 3;
+ triangles += geometry.index.count;
+ } else {
+ vertices += geometry.attributes.position.count;
+ triangles += geometry.attributes.position.count / 3;
+ }
+ }
+ }
+ });
+ }
+
+ var objectsText = UI.get('objectsText', this.id);
+ var verticesText = UI.get('verticesText', this.id);
+ var trianglesText = UI.get('trianglesText', this.id);
+
+ objectsText.setValue(objects.format());
+ verticesText.setValue(vertices.format());
+ trianglesText.setValue(triangles.format());
+};
+
+export default StatusBar;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/Toolbar.js b/ShadowEditor.Core/src/editor/Toolbar.js
new file mode 100644
index 00000000..fd7b9a7c
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/Toolbar.js
@@ -0,0 +1,134 @@
+import { UI } from '../third_party';
+import ModelWindow from './window/ModelWindow';
+
+/**
+ * 工具栏
+ * @author tengge / https://github.com/tengge1
+ */
+function Toolbar(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+Toolbar.prototype = Object.create(UI.Control.prototype);
+Toolbar.prototype.constructor = Toolbar;
+
+Toolbar.prototype.render = function () {
+ var _this = this;
+
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'toolbar',
+ children: [{
+ xtype: 'iconbutton',
+ id: 'selectBtn',
+ scope: this.id,
+ icon: 'icon-select',
+ title: '选择',
+ onClick: this.enterSelectMode.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ id: 'translateBtn',
+ scope: this.id,
+ icon: 'icon-translate',
+ cls: 'Button IconButton selected',
+ title: '平移(W)',
+ onClick: this.enterTranslateMode.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ id: 'rotateBtn',
+ scope: this.id,
+ icon: 'icon-rotate',
+ title: '旋转(E)',
+ onClick: this.enterRotateMode.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ id: 'scaleBtn',
+ scope: this.id,
+ icon: 'icon-scale',
+ title: '缩放(R)',
+ onClick: this.enterScaleMode.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'iconbutton',
+ icon: 'icon-model-view',
+ title: '模型',
+ onClick: this.showModelWindow.bind(this)
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`changeMode.${this.id}`, this.onChangeMode.bind(this));
+};
+
+// --------------------------------- 选择模式 -------------------------------------
+
+Toolbar.prototype.enterSelectMode = function () {
+ this.app.call('changeMode', this, 'select');
+};
+
+// -------------------------------- 平移模式 --------------------------------------
+
+Toolbar.prototype.enterTranslateMode = function () {
+ this.app.call('changeMode', this, 'translate');
+};
+
+// -------------------------------- 旋转模式 ---------------------------------------
+
+Toolbar.prototype.enterRotateMode = function () {
+ this.app.call('changeMode', this, 'rotate');
+};
+
+// -------------------------------- 缩放模式 ---------------------------------------
+
+Toolbar.prototype.enterScaleMode = function () {
+ this.app.call('changeMode', this, 'scale');
+};
+
+// ------------------------------ 模式改变事件 -------------------------------------
+
+Toolbar.prototype.onChangeMode = function (mode) {
+ var selectBtn = UI.get('selectBtn', this.id);
+ var translateBtn = UI.get('translateBtn', this.id);
+ var rotateBtn = UI.get('rotateBtn', this.id);
+ var scaleBtn = UI.get('scaleBtn', this.id);
+
+ selectBtn.unselect();
+ translateBtn.unselect();
+ rotateBtn.unselect();
+ scaleBtn.unselect();
+
+ switch (mode) {
+ case 'select':
+ selectBtn.select();
+ break;
+ case 'translate':
+ translateBtn.select();
+ break;
+ case 'rotate':
+ rotateBtn.select();
+ break;
+ case 'scale':
+ scaleBtn.select();
+ break;
+ }
+};
+
+// -------------------------------- 模型窗口 ---------------------------------------
+
+Toolbar.prototype.showModelWindow = function () {
+ if (this.modelWindow == null) {
+ this.modelWindow = new ModelWindow({
+ parent: this.app.container,
+ app: this.app
+ });
+ this.modelWindow.render();
+ }
+ this.modelWindow.show();
+};
+
+export default Toolbar;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/Viewport.js b/ShadowEditor.Core/src/editor/Viewport.js
new file mode 100644
index 00000000..ba79eba3
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/Viewport.js
@@ -0,0 +1,26 @@
+import { UI } from '../third_party';
+
+/**
+ * 场景编辑区
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Viewport(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+Viewport.prototype = Object.create(UI.Control.prototype);
+Viewport.prototype.constructor = Viewport;
+
+Viewport.prototype.render = function () {
+ this.container = UI.create({
+ xtype: 'div',
+ id: 'viewport',
+ parent: this.parent,
+ cls: 'viewport'
+ });
+ this.container.render();
+};
+
+export default Viewport;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/AnimationPanel.js b/ShadowEditor.Core/src/editor/bottom/AnimationPanel.js
new file mode 100644
index 00000000..3fcab3b9
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/AnimationPanel.js
@@ -0,0 +1,432 @@
+import { UI } from '../../third_party';
+import AnimationGroup from '../../animation/AnimationGroup';
+import Animation from '../../animation/Animation';
+
+const STOP = 0;
+const PLAY = 1;
+const PAUSE = 2;
+
+/**
+ * 动画面板
+ * @author tengge / https://github.com/tengge1
+ */
+function AnimationPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+
+ this.status = STOP;
+ this.sliderLeft = 0;
+ this.speed = 4;
+
+ this.isDragging = false;
+};
+
+AnimationPanel.prototype = Object.create(UI.Control.prototype);
+AnimationPanel.prototype.constructor = AnimationPanel;
+
+AnimationPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'animation-panel',
+ children: [{
+ xtype: 'div',
+ cls: 'controls',
+ children: [{
+ xtype: 'iconbutton',
+ icon: 'icon-add',
+ onClick: this.onAddGroup.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ icon: 'icon-delete',
+ onClick: this.onRemoveGroup.bind(this)
+ }, {
+ xtype: 'div',
+ style: {
+ width: '2px',
+ height: '20px',
+ borderLeft: '1px solid #aaa',
+ borderRight: '1px solid #aaa',
+ boxSizing: 'border-box',
+ margin: '5px 8px'
+ }
+ }, {
+ xtype: 'iconbutton',
+ icon: 'icon-backward',
+ onClick: this.onBackward.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ id: 'btnPlay',
+ scope: this.id,
+ icon: 'icon-play',
+ onClick: this.onPlay.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ id: 'btnPause',
+ scope: this.id,
+ icon: 'icon-pause',
+ style: {
+ display: 'none'
+ },
+ onClick: this.onPause.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ icon: 'icon-forward',
+ onClick: this.onForward.bind(this)
+ }, {
+ xtype: 'iconbutton',
+ icon: 'icon-stop',
+ onClick: this.onStop.bind(this)
+ }, {
+ xtype: 'text',
+ id: 'time',
+ scope: this.id,
+ style: {
+ marginLeft: '8px',
+ color: '#555',
+ fontSize: '12px'
+ },
+ text: '00:00'
+ }, {
+ xtype: 'text',
+ id: 'speed',
+ scope: this.id,
+ style: {
+ marginLeft: '8px',
+ color: '#aaa',
+ fontSize: '12px'
+ },
+ text: 'X 1'
+ }, {
+ xtype: 'toolbarfiller'
+ }, {
+ xtype: 'text',
+ scope: this.id,
+ style: {
+ marginLeft: '8px',
+ color: '#aaa',
+ fontSize: '12px'
+ },
+ text: '说明:双击时间轴下方添加动画。'
+ }]
+ }, {
+ xtype: 'div',
+ cls: 'box',
+ children: [{
+ xtype: 'div',
+ cls: 'left-area',
+ id: 'groupInfo',
+ scope: this.id
+ }, {
+ xtype: 'div',
+ cls: 'right-area',
+ children: [{
+ xtype: 'timeline',
+ id: 'timeline',
+ cls: 'timeline',
+ scope: this.id
+ }, {
+ xtype: 'div',
+ cls: 'groups',
+ id: 'groups',
+ scope: this.id,
+ children: []
+ }, {
+ xtype: 'div',
+ cls: 'slider',
+ id: 'slider',
+ scope: this.id
+ }]
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+};
+
+AnimationPanel.prototype.onAppStarted = function () {
+ var timeline = UI.get('timeline', this.id);
+ var groups = UI.get('groups', this.id);
+
+ timeline.updateUI();
+ groups.dom.style.width = timeline.dom.clientWidth + 'px';
+
+ groups.dom.addEventListener(`click`, this.onClick.bind(this));
+ groups.dom.addEventListener(`dblclick`, this.onDblClick.bind(this));
+ groups.dom.addEventListener(`mousedown`, this.onMouseDown.bind(this));
+ groups.dom.addEventListener(`mousemove`, this.onMouseMove.bind(this));
+ document.body.addEventListener(`mouseup`, this.onMouseUp.bind(this));
+
+ this.app.on(`animationChanged.${this.id}`, this.updateUI.bind(this));
+
+ this.app.on(`resetAnimation.${this.id}`, this.onResetAnimation.bind(this));
+ this.app.on(`startAnimation.${this.id}`, this.onPlay.bind(this));
+};
+
+AnimationPanel.prototype.updateUI = function () {
+ var animations = this.app.editor.animation.getAnimationGroups();
+
+ var groupInfo = UI.get('groupInfo', this.id);
+ var timeline = UI.get('timeline', this.id);
+ var groups = UI.get('groups', this.id);
+
+ while (groupInfo.dom.children.length) {
+ var child = groupInfo.dom.children[0];
+ groupInfo.dom.removeChild(child);
+ }
+
+ while (groups.dom.children.length) {
+ var child = groups.dom.children[0];
+ child.data = null;
+ groups.dom.removeChild(child);
+ }
+
+ animations.forEach(n => {
+ // 动画组信息区
+ var groupName = document.createElement('div');
+ groupName.className = 'group-info';
+ groupName.innerHTML = `${n.name}`;
+ groupInfo.dom.appendChild(groupName);
+
+ // 动画区
+ var group = document.createElement('div');
+ group.className = 'group';
+ group.setAttribute('droppable', true);
+ group.data = n;
+ group.addEventListener('dragenter', this.onDragEnterGroup.bind(this));
+ group.addEventListener('dragover', this.onDragOverGroup.bind(this));
+ group.addEventListener('dragleave', this.onDragLeaveGroup.bind(this));
+ group.addEventListener('drop', this.onDropGroup.bind(this));
+ groups.dom.appendChild(group);
+
+ n.animations.forEach(m => {
+ var item = document.createElement('div');
+ item.data = m;
+ item.className = 'item';
+ item.setAttribute('draggable', true);
+ item.setAttribute('droppable', false);
+ item.style.left = m.beginTime * timeline.scale + 'px';
+ item.style.width = (m.endTime - m.beginTime) * timeline.scale + 'px';
+ item.innerHTML = m.name;
+ item.addEventListener('dragstart', this.onDragStartAnimation.bind(this));
+ item.addEventListener('dragend', this.onDragEndAnimation.bind(this));
+ group.appendChild(item);
+ });
+ });
+};
+
+AnimationPanel.prototype.updateSlider = function () {
+ var timeline = UI.get('timeline', this.id);
+ var slider = UI.get('slider', this.id);
+ var time = UI.get('time', this.id);
+ var speed = UI.get('speed', this.id);
+
+ slider.dom.style.left = this.sliderLeft + 'px';
+
+ var animationTime = this.sliderLeft / timeline.scale;
+
+ var minute = ('0' + Math.floor(animationTime / 60)).slice(-2);
+ var second = ('0' + Math.floor(animationTime % 60)).slice(-2);
+
+ time.setValue(`${minute}:${second}`);
+
+ if (this.speed >= 4) {
+ speed.dom.innerHTML = `X ${this.speed / 4}`;
+ } else {
+ speed.dom.innerHTML = `X 1/${4 / this.speed}`;
+ }
+
+ this.app.call('animationTime', this, animationTime);
+};
+
+AnimationPanel.prototype.onAnimate = function () {
+ var timeline = UI.get('timeline', this.id);
+ this.sliderLeft += this.speed / 4;
+
+ if (this.sliderLeft >= timeline.dom.clientWidth) {
+ this.sliderLeft = 0;
+ }
+
+ this.updateSlider();
+};
+
+AnimationPanel.prototype.onAddGroup = function () {
+ var group = new AnimationGroup();
+ this.app.editor.animation.add(group);
+ this.updateUI();
+};
+
+AnimationPanel.prototype.onRemoveGroup = function () {
+ var inputs = document.querySelectorAll('.animation-panel .left-area input:checked');
+
+ var uuids = [];
+ inputs.forEach(n => {
+ uuids.push(n.getAttribute('data-uuid'));
+ });
+
+ if (uuids.length === 0) {
+ UI.msg('请勾选需要删除的组!');
+ return;
+ }
+
+ UI.confirm('询问', '删除组会删除组上的所有动画。是否删除?', (event, btn) => {
+ if (btn === 'ok') {
+ uuids.forEach(n => {
+ this.app.editor.animation.removeByUUID(n);
+ });
+ this.updateUI();
+ }
+ });
+};
+
+// ----------------------------------- 播放器事件 -------------------------------------------
+
+AnimationPanel.prototype.onPlay = function () {
+ if (this.status === PLAY) {
+ return;
+ }
+ this.status = PLAY;
+
+ UI.get('btnPlay', this.id).dom.style.display = 'none';
+ UI.get('btnPause', this.id).dom.style.display = '';
+
+ this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
+};
+
+AnimationPanel.prototype.onPause = function () {
+ if (this.status === PAUSE) {
+ return;
+ }
+ this.status = PAUSE;
+
+ UI.get('btnPlay', this.id).dom.style.display = '';
+ UI.get('btnPause', this.id).dom.style.display = 'none';
+
+ this.app.on(`animate.${this.id}`, null);
+ this.updateSlider();
+};
+
+AnimationPanel.prototype.onForward = function () {
+ if (this.speed >= 16) {
+ return;
+ }
+ this.speed *= 2;
+};
+
+AnimationPanel.prototype.onBackward = function () {
+ if (this.speed <= 1) {
+ return;
+ }
+ this.speed /= 2;
+};
+
+AnimationPanel.prototype.onStop = function () {
+ if (this.status === STOP) {
+ return;
+ }
+ this.status = STOP;
+
+ UI.get('btnPlay', this.id).dom.style.display = '';
+ UI.get('btnPause', this.id).dom.style.display = 'none';
+
+ this.app.on(`animate.${this.id}`, null);
+ this.sliderLeft = 0;
+ this.updateSlider();
+};
+
+AnimationPanel.prototype.onResetAnimation = function () {
+ this.onStop();
+ this.speed = 4;
+};
+
+AnimationPanel.prototype.onClick = function (event) {
+ if (event.target.data.type === 'AnimationGroup') {
+ return;
+ }
+ this.app.call('tabSelected', this, 'animation');
+ this.app.call('animationSelected', this, event.target.data);
+};
+
+AnimationPanel.prototype.onDblClick = function (event) {
+ var timeline = UI.get('timeline', this.id);
+
+ if (event.target.data && event.target.data.type === 'AnimationGroup') {
+ event.stopPropagation();
+
+ var animation = new Animation({
+ beginTime: event.offsetX / timeline.scale,
+ endTime: (event.offsetX + 80) / timeline.scale
+ });
+
+ event.target.data.add(animation);
+
+ this.app.call('animationChanged', this);
+ }
+};
+
+AnimationPanel.prototype.onMouseDown = function () {
+ if (this.isDragging) {
+ return;
+ }
+ this.isDragging = true;
+};
+
+AnimationPanel.prototype.onMouseMove = function () {
+
+};
+
+AnimationPanel.prototype.onMouseUp = function () {
+ this.isDragging = false;
+};
+
+// ----------------------- 拖动动画事件 ---------------------------------------------
+
+AnimationPanel.prototype.onDragStartAnimation = function (event) {
+ event.dataTransfer.setData('uuid', event.target.data.uuid);
+ event.dataTransfer.setData('offsetX', event.offsetX);
+};
+
+AnimationPanel.prototype.onDragEndAnimation = function (event) {
+ event.dataTransfer.clearData();
+};
+
+AnimationPanel.prototype.onDragEnterGroup = function (event) {
+ event.preventDefault();
+};
+
+AnimationPanel.prototype.onDragOverGroup = function (event) {
+ event.preventDefault();
+};
+
+AnimationPanel.prototype.onDragLeaveGroup = function (event) {
+ event.preventDefault();
+};
+
+AnimationPanel.prototype.onDropGroup = function (event) {
+ event.preventDefault();
+ var uuid = event.dataTransfer.getData('uuid');
+ var offsetX = event.dataTransfer.getData('offsetX');
+
+ var groups = this.app.editor.animation.getAnimationGroups();
+ var group = groups.filter(n => n.animations.findIndex(m => m.uuid === uuid) > -1)[0];
+ var animation = group.animations.filter(n => n.uuid === uuid)[0];
+ group.remove(animation);
+
+ var timeline = UI.get('timeline', this.id);
+ var length = animation.endTime - animation.beginTime;
+ animation.beginTime = (event.offsetX - offsetX) / timeline.scale;
+ animation.endTime = animation.beginTime + length;
+
+ if (event.target.data instanceof Animation) { // 拖动到其他动画上
+ event.target.parentElement.data.add(animation);
+ } else { // 拖动到动画组上
+ event.target.data.add(animation);
+ }
+
+ this.updateUI();
+};
+
+export default AnimationPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/AudioPanel.js b/ShadowEditor.Core/src/editor/bottom/AudioPanel.js
new file mode 100644
index 00000000..33dbddb3
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/AudioPanel.js
@@ -0,0 +1,30 @@
+import { UI } from '../../third_party';
+
+/**
+ * 纹理面板
+ * @author tengge / https://github.com/tengge1
+ */
+function AudioPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+AudioPanel.prototype = Object.create(UI.Control.prototype);
+AudioPanel.prototype.constructor = AudioPanel;
+
+AudioPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ position: 'relative'
+ },
+ children: []
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default AudioPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/BottomPanel.js b/ShadowEditor.Core/src/editor/bottom/BottomPanel.js
new file mode 100644
index 00000000..d3c1ea6a
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/BottomPanel.js
@@ -0,0 +1,197 @@
+import { Control } from '../../third_party';
+import AnimationPanel from './AnimationPanel';
+import ModelPanel from './ModelPanel';
+import TexturePanel from './TexturePanel';
+import AudioPanel from './AudioPanel';
+import ParticlePanel from './ParticlePanel';
+import LogPanel from './LogPanel';
+
+/**
+ * 底部面板
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function BottomPanel(options) {
+ Control.call(this, options);
+ this.app = options.app;
+};
+
+BottomPanel.prototype = Object.create(Control.prototype);
+BottomPanel.prototype.constructor = BottomPanel;
+
+BottomPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ cls: 'sidebar bottomPanel',
+ parent: this.parent,
+ children: [{
+ xtype: 'div',
+ cls: 'tabs',
+ style: {
+ position: 'sticky',
+ top: 0,
+ zIndex: 20
+ },
+ children: [{
+ xtype: 'text',
+ id: 'animationTab',
+ text: '动画',
+ onClick: () => {
+ this.selectTab('animation');
+ }
+ }, {
+ xtype: 'text',
+ id: 'modelTab',
+ text: '模型',
+ onClick: () => {
+ this.selectTab('model');
+ }
+ }, {
+ xtype: 'text',
+ id: 'textureTab',
+ text: '纹理',
+ onClick: () => {
+ this.selectTab('texture');
+ }
+ }, {
+ xtype: 'text',
+ id: 'audioTab',
+ text: '音频',
+ onClick: () => {
+ this.selectTab('audio');
+ }
+ }, {
+ xtype: 'text',
+ id: 'particleTab',
+ text: '粒子',
+ onClick: () => {
+ this.selectTab('particle');
+ }
+ }, {
+ xtype: 'text',
+ id: 'logTab',
+ text: '日志',
+ onClick: () => {
+ this.selectTab('log');
+ }
+ }]
+ }, {
+ xtype: 'div',
+ id: 'animationPanel',
+ style: {
+ flex: 1
+ },
+ children: [
+ new AnimationPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'modelPanel',
+ style: {
+ flex: 1
+ },
+ children: [
+ new ModelPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'texturePanel',
+ style: {
+ flex: 1
+ },
+ children: [
+ new TexturePanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'audioPanel',
+ style: {
+ flex: 1
+ },
+ children: [
+ new AudioPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'particlePanel',
+ style: {
+ flex: 1
+ },
+ children: [
+ new ParticlePanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'logPanel',
+ children: [
+ new LogPanel({ app: this.app })
+ ]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`appStarted.${this.id}`, () => {
+ this.selectTab('animation');
+ });
+};
+
+BottomPanel.prototype.selectTab = function (tabName) {
+ var animationTab = UI.get('animationTab');
+ var modelTab = UI.get('modelTab');
+ var textureTab = UI.get('textureTab');
+ var audioTab = UI.get('audioTab');
+ var particleTab = UI.get('particleTab');
+ var logTab = UI.get('logTab');
+
+ var animationPanel = UI.get('animationPanel');
+ var modelPanel = UI.get('modelPanel');
+ var texturePanel = UI.get('texturePanel');
+ var audioPanel = UI.get('audioPanel');
+ var particlePanel = UI.get('particlePanel');
+ var logPanel = UI.get('logPanel');
+
+ animationTab.dom.className = '';
+ modelTab.dom.className = '';
+ textureTab.dom.className = '';
+ audioTab.dom.className = '';
+ particleTab.dom.className = '';
+ logTab.dom.className = '';
+
+ animationPanel.dom.style.display = 'none';
+ modelPanel.dom.style.display = 'none';
+ texturePanel.dom.style.display = 'none';
+ audioPanel.dom.style.display = 'none';
+ particlePanel.dom.style.display = 'none';
+ logPanel.dom.style.display = 'none';
+
+ switch (tabName) {
+ case 'animation':
+ animationTab.dom.className = 'selected';
+ animationPanel.dom.style.display = '';
+ break;
+ case 'model':
+ modelTab.dom.className = 'selected';
+ modelPanel.dom.style.display = '';
+ break;
+ case 'texture':
+ textureTab.dom.className = 'selected';
+ texturePanel.dom.style.display = '';
+ break;
+ case 'audio':
+ audioTab.dom.className = 'selected';
+ audioPanel.dom.style.display = '';
+ break;
+ case 'particle':
+ particleTab.dom.className = 'selected';
+ particlePanel.dom.style.display = '';
+ break;
+ case 'log':
+ logTab.dom.className = 'selected';
+ logPanel.dom.style.display = '';
+ break;
+ }
+};
+
+export default BottomPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/LogPanel.js b/ShadowEditor.Core/src/editor/bottom/LogPanel.js
new file mode 100644
index 00000000..c925048c
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/LogPanel.js
@@ -0,0 +1,85 @@
+import { UI } from '../../third_party';
+
+/**
+ * 日志面板
+ * @author tengge / https://github.com/tengge1
+ */
+function LogPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+LogPanel.prototype = Object.create(UI.Control.prototype);
+LogPanel.prototype.constructor = LogPanel;
+
+LogPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ position: 'relative'
+ },
+ children: [{
+ xtype: 'button',
+ text: '清空',
+ onClick: this.onClearLog.bind(this)
+ }, {
+ xtype: 'br'
+ }, {
+ xtype: 'div',
+ style: {
+ height: '140px',
+ marginTop: '8px',
+ backgroundColor: '#fff',
+ overflowY: 'auto'
+ },
+ id: 'logContent'
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`log.${this.id}`, this.onLog.bind(this));
+};
+
+LogPanel.prototype.onLog = function (content, type) {
+ var dom = UI.get('logContent').dom;
+
+ var date = new Date();
+ var hour = date.getHours();
+ var minute = date.getMinutes();
+ var second = date.getSeconds();
+
+ hour = hour < 10 ? '0' + hour : hour;
+ minute = minute < 10 ? '0' + minute : minute;
+ second = second < 10 ? '0' + second : second;
+
+ content = `${hour}:${minute}:${second}${content}`;
+
+ var box = document.createElement('div');
+ box.innerHTML = content;
+
+ if (dom.children.length === 0) {
+ dom.appendChild(box);
+ } else {
+ dom.insertBefore(box, dom.children[0]);
+ }
+
+ if (type === 'warn') {
+ box.style.backgroundColor = '#fffbe5';
+ box.style.color = '#5c3c00';
+ } else if (type === 'error') {
+ box.style.backgroundColor = '#fff0f0';
+ box.style.color = '#ff0000';
+ }
+};
+
+LogPanel.prototype.onClearLog = function () {
+ var dom = UI.get('logContent').dom;
+ dom.innerHTML = '';
+ this.onLog('清空日志');
+};
+
+export default LogPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/ModelPanel.js b/ShadowEditor.Core/src/editor/bottom/ModelPanel.js
new file mode 100644
index 00000000..6b382e72
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/ModelPanel.js
@@ -0,0 +1,30 @@
+import { UI } from '../../third_party';
+
+/**
+ * 模型面板
+ * @author tengge / https://github.com/tengge1
+ */
+function ModelPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+ModelPanel.prototype = Object.create(UI.Control.prototype);
+ModelPanel.prototype.constructor = ModelPanel;
+
+ModelPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ position: 'relative'
+ },
+ children: []
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default ModelPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/ParticlePanel.js b/ShadowEditor.Core/src/editor/bottom/ParticlePanel.js
new file mode 100644
index 00000000..755d2558
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/ParticlePanel.js
@@ -0,0 +1,30 @@
+import { UI } from '../../third_party';
+
+/**
+ * 粒子面板
+ * @author tengge / https://github.com/tengge1
+ */
+function ParticlePanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+ParticlePanel.prototype = Object.create(UI.Control.prototype);
+ParticlePanel.prototype.constructor = ParticlePanel;
+
+ParticlePanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ position: 'relative'
+ },
+ children: []
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default ParticlePanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/bottom/TexturePanel.js b/ShadowEditor.Core/src/editor/bottom/TexturePanel.js
new file mode 100644
index 00000000..080b54f6
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/bottom/TexturePanel.js
@@ -0,0 +1,30 @@
+import { UI } from '../../third_party';
+
+/**
+ * 纹理面板
+ * @author tengge / https://github.com/tengge1
+ */
+function TexturePanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+TexturePanel.prototype = Object.create(UI.Control.prototype);
+TexturePanel.prototype.constructor = TexturePanel;
+
+TexturePanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ position: 'relative'
+ },
+ children: []
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default TexturePanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/control/TextureSelectControl.js b/ShadowEditor.Core/src/editor/control/TextureSelectControl.js
new file mode 100644
index 00000000..cf9dbeeb
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/control/TextureSelectControl.js
@@ -0,0 +1,119 @@
+import { Control } from '../../third_party';
+import TextureWindow from '../window/TextureWindow';
+
+/**
+ * 纹理选择控件
+ * @param {*} options
+ */
+function TextureSelectControl(options) {
+ Control.call(this, options);
+
+ this.app = options.app;
+
+ this.texture = null;
+
+ this.onChange = options.onChange || null;
+}
+
+TextureSelectControl.prototype = Object.create(Control.prototype);
+TextureSelectControl.prototype.constructor = TextureSelectControl;
+
+TextureSelectControl.prototype.render = function () {
+ this.dom = document.createElement('div');
+ this.dom.className = 'Texture';
+
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = 32;
+ this.canvas.height = 16;
+ this.dom.appendChild(this.canvas);
+
+ this.canvas.addEventListener('click', this.onClick.bind(this));
+
+ this.name = document.createElement('input');
+ this.name.disabled = true;
+ this.dom.appendChild(this.name);
+
+ this.parent.appendChild(this.dom);
+};
+
+TextureSelectControl.prototype.updateUI = function () {
+ var canvas = this.dom.children[0];
+ var name = this.dom.children[1];
+ var context = canvas.getContext('2d');
+
+ var texture = this.texture;
+
+ if (texture !== undefined && texture !== null) {
+ var image = texture.image;
+
+ if (image !== undefined && image.width > 0) {
+ name.value = texture.name;
+
+ var scale = canvas.width / image.width;
+ context.drawImage(image, 0, 0, image.width * scale, image.height * scale);
+ } else {
+ name.value = '无图片';
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+ } else {
+ name.value = '';
+
+ if (context !== null) {
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ }
+ }
+};
+
+TextureSelectControl.prototype.getValue = function () {
+ return this.texture;
+};
+
+TextureSelectControl.prototype.setValue = function (texture) {
+ this.texture = texture;
+ this.updateUI();
+};
+
+TextureSelectControl.prototype.onClick = function () {
+ if (this.window === undefined) {
+ this.window = new TextureWindow({
+ app: this.app,
+ onSelect: this.onSelect.bind(this)
+ });
+ this.window.render();
+ }
+ this.window.show();
+};
+
+TextureSelectControl.prototype.onSelect = function (data) {
+ var urls = data.Url.split(';'); // 立体贴图data.Url多于一张,只取第一个。
+
+ if (data.Type === 'video') { // 视频贴图
+ var video = document.createElement('video');
+ video.src = `${this.app.options.server}${urls[0]}`;
+ video.loop = 'loop';
+ video.autoplay = 'autoplay';
+ video.onplay = () => {
+ var texture = new THREE.VideoTexture(video);
+ texture.minFilter = THREE.LinearFilter;
+ texture.magFilter = THREE.LinearFilter;
+ texture.format = THREE.RGBFormat;
+ this.texture = texture;
+ this.texture.name = data.Name;
+ this.window.hide();
+ this.updateUI();
+ this.onChange();
+ }
+ } else { // 其他
+ var loader = new THREE.TextureLoader();
+ loader.load(`${this.app.options.server}${urls[0]}`, texture => {
+ this.texture = texture;
+ this.texture.name = data.Name;
+ this.window.hide();
+ this.updateUI();
+ this.onChange();
+ });
+ }
+};
+
+export default TextureSelectControl;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/AssetMenu.js b/ShadowEditor.Core/src/editor/menubar/AssetMenu.js
new file mode 100644
index 00000000..e7511dc3
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/AssetMenu.js
@@ -0,0 +1,282 @@
+import { UI } from '../../third_party';
+import ModelWindow from '../window/ModelWindow';
+import TextureWindow from '../window/TextureWindow';
+import AudioWindow from '../window/AudioWindow';
+import MMDWindow from '../window/MMDWindow';
+import StringUtils from '../../utils/StringUtils';
+
+/**
+ * 资源菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function AssetMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+AssetMenu.prototype = Object.create(UI.Control.prototype);
+AssetMenu.prototype.constructor = AssetMenu;
+
+AssetMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '资源'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '模型管理',
+ cls: 'option',
+ onClick: this.onManageModel.bind(this)
+ }, {
+ xtype: 'div',
+ html: '纹理管理',
+ cls: 'option',
+ onClick: this.onManageTexture.bind(this)
+ }, {
+ xtype: 'div',
+ html: '音频管理',
+ cls: 'option',
+ onClick: this.onManageAudio.bind(this)
+ }, {
+ xtype: 'div',
+ html: 'MMD资源管理',
+ cls: 'option',
+ onClick: this.onManageMMD.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '导出几何体',
+ cls: 'option',
+ onClick: this.onExportGeometry.bind(this)
+ }, {
+ xtype: 'div',
+ html: '导出物体',
+ cls: 'option',
+ onClick: this.onExportObject.bind(this)
+ }, {
+ xtype: 'div',
+ html: '导出场景',
+ cls: 'option',
+ onClick: this.onExportScene.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '导出gltf文件',
+ cls: 'option',
+ onClick: this.onExportGLTF.bind(this)
+ }, {
+ xtype: 'div',
+ html: '导出obj文件',
+ cls: 'option',
+ onClick: this.onExportOBJ.bind(this)
+ }, {
+ xtype: 'div',
+ html: '导出ply文件',
+ cls: 'option',
+ onClick: this.onExportPLY.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'mExportSTLB',
+ html: '导出stl二进制文件',
+ cls: 'option',
+ onClick: this.onExportSTLB.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'mExportSTL',
+ html: '导出stl文件',
+ cls: 'option',
+ onClick: this.onExportSTL.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// --------------------------------- 模型管理 --------------------------------------
+
+AssetMenu.prototype.onManageModel = function () {
+ if (this.modelWindow == null) {
+ this.modelWindow = new ModelWindow({ parent: this.app.container, app: this.app });
+ this.modelWindow.render();
+ }
+ this.modelWindow.show();
+};
+
+// --------------------------------- 纹理管理 --------------------------------------
+
+AssetMenu.prototype.onManageTexture = function () {
+ if (this.textureWindow == null) {
+ this.textureWindow = new TextureWindow({ parent: this.app.container, app: this.app });
+ this.textureWindow.render();
+ }
+ this.textureWindow.show();
+};
+
+// --------------------------------- 音频管理 --------------------------------------
+
+AssetMenu.prototype.onManageAudio = function () {
+ if (this.audioWindow == null) {
+ this.audioWindow = new AudioWindow({ parent: this.app.container, app: this.app });
+ this.audioWindow.render();
+ }
+ this.audioWindow.show();
+};
+
+// ------------------------------- MMD资源管理 --------------------------------------
+
+AssetMenu.prototype.onManageMMD = function () {
+ if (this.mmdWindow == null) {
+ this.mmdWindow = new MMDWindow({ parent: this.app.container, app: this.app });
+ this.mmdWindow.render();
+ }
+ this.mmdWindow.show();
+};
+
+// ------------------------------- 导出几何体 ----------------------------------------
+
+AssetMenu.prototype.onExportGeometry = function () {
+ var editor = this.app.editor;
+
+ var object = editor.selected;
+
+ if (object === null) {
+ UI.msg('请选择物体');
+ return;
+ }
+
+ var geometry = object.geometry;
+
+ if (geometry === undefined) {
+ UI.msg('选中的对象不具有Geometry属性。');
+ return;
+ }
+
+ var output = geometry.toJSON();
+
+ try {
+ output = JSON.stringify(output, parseNumber, '\t');
+ output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
+ } catch (e) {
+ output = JSON.stringify(output);
+ }
+
+ StringUtils.saveString(output, 'geometry.json');
+};
+
+// ------------------------------- 导出物体 ------------------------------------------
+
+AssetMenu.prototype.onExportObject = function () {
+ var editor = this.app.editor;
+
+ var object = editor.selected;
+
+ if (object === null) {
+ UI.msg('请选择对象');
+ return;
+ }
+
+ var output = object.toJSON();
+
+ try {
+ output = JSON.stringify(output, parseNumber, '\t');
+ output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
+ } catch (e) {
+ output = JSON.stringify(output);
+ }
+
+ StringUtils.saveString(output, 'model.json');
+};
+
+// ------------------------------- 导出场景 ------------------------------------------
+
+AssetMenu.prototype.onExportScene = function () {
+ var editor = this.app.editor;
+
+ var output = editor.scene.toJSON();
+
+ try {
+ output = JSON.stringify(output, parseNumber, '\t');
+ output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
+ } catch (e) {
+ output = JSON.stringify(output);
+ }
+
+ StringUtils.saveString(output, 'scene.json');
+};
+
+// ------------------------------ 导出gltf文件 ----------------------------------------
+
+AssetMenu.prototype.onExportGLTF = function () {
+ var exporter = new THREE.GLTFExporter();
+
+ exporter.parse(app.editor.scene, function (result) {
+ StringUtils.saveString(JSON.stringify(result), 'model.gltf');
+ });
+};
+
+// ------------------------------ 导出obj文件 -----------------------------------------
+
+AssetMenu.prototype.onExportOBJ = function () {
+ var editor = this.app.editor;
+
+ var object = editor.selected;
+
+ if (object === null) {
+ UI.msg('请选择对象');
+ return;
+ }
+
+ var exporter = new THREE.OBJExporter();
+ StringUtils.saveString(exporter.parse(object), 'model.obj');
+};
+
+// ------------------------------- 导出ply文件 ----------------------------------------
+
+AssetMenu.prototype.onExportPLY = function () {
+ var editor = this.app.editor;
+
+ var object = editor.selected;
+
+ if (object === null) {
+ UI.msg('请选择对象');
+ return;
+ }
+
+ var exporter = new THREE.PLYExporter();
+ StringUtils.saveString(exporter.parse(object, {
+ excludeAttributes: ['normal', 'uv', 'color', 'index']
+ }), 'model.ply');
+};
+
+// ------------------------------- 导出stl二进制文件 -----------------------------------
+
+AssetMenu.prototype.onExportSTLB = function () {
+ var editor = this.app.editor;
+
+ var exporter = new THREE.STLBinaryExporter();
+
+ StringUtils.saveString(exporter.parse(editor.scene), 'model.stl');
+};
+
+// ------------------------------- 导出stl文件 -----------------------------------------
+
+AssetMenu.prototype.onExportSTL = function () {
+ var editor = this.app.editor;
+
+ var exporter = new THREE.STLExporter();
+
+ StringUtils.saveString(exporter.parse(editor.scene), 'model.stl');
+};
+
+export default AssetMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/ComponentMenu.js b/ShadowEditor.Core/src/editor/menubar/ComponentMenu.js
new file mode 100644
index 00000000..4587bc59
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/ComponentMenu.js
@@ -0,0 +1,205 @@
+import { UI } from '../../third_party';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import Sky from '../../object/component/Sky';
+import Fire from '../../object/component/Fire';
+import Water from '../../object/water/Water';
+import Smoke from '../../object/component/Smoke';
+import ParticleEmitter from '../../object/component/ParticleEmitter';
+import PlysicsUtils from '../../physics/PlysicsUtils';
+
+/**
+ * 组件菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ComponentMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+ComponentMenu.prototype = Object.create(UI.Control.prototype);
+ComponentMenu.prototype.constructor = ComponentMenu;
+
+ComponentMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '组件'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '背景音乐',
+ cls: 'option',
+ onClick: this.onAddBackgroundMusic.bind(this)
+ }, {
+ xtype: 'div',
+ html: '粒子发射器',
+ cls: 'option',
+ onClick: this.ParticleEmitter.bind(this)
+ }, {
+ xtype: 'div',
+ html: '天空',
+ cls: 'option',
+ onClick: this.onAddSky.bind(this)
+ }, {
+ xtype: 'div',
+ html: '火焰',
+ cls: 'option',
+ onClick: this.onAddFire.bind(this)
+ }, {
+ xtype: 'div',
+ html: '液体',
+ cls: 'option',
+ onClick: this.onAddWater.bind(this)
+ }, {
+ xtype: 'div',
+ html: '烟',
+ cls: 'option',
+ onClick: this.onAddSmoke.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '刚体',
+ cls: 'option',
+ onClick: this.addRigidBody.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '曲线编辑器',
+ cls: 'option',
+ onClick: this.curveEditor.bind(this)
+ }, {
+ xtype: 'div',
+ html: '发型编辑器',
+ cls: 'option',
+ onClick: this.hairEditor.bind(this)
+ }, {
+ xtype: 'div',
+ html: '服装编辑器',
+ cls: 'option',
+ onClick: this.clothingEditor.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// ---------------------------- 添加背景音乐 ----------------------------------
+
+ComponentMenu.prototype.onAddBackgroundMusic = function () {
+ var editor = this.app.editor;
+ var listener = editor.audioListener;
+
+ var audio = new THREE.Audio(listener);
+ audio.name = `背景音乐`;
+ audio.autoplay = false;
+ audio.setLoop(true);
+ audio.setVolume(1.0);
+
+ audio.userData.autoplay = true;
+
+ this.app.editor.execute(new AddObjectCommand(audio));
+};
+
+// ---------------------------- 添加粒子发射器 --------------------------------------------
+
+ComponentMenu.prototype.ParticleEmitter = function () {
+ var emitter = new ParticleEmitter();
+ this.app.editor.execute(new AddObjectCommand(emitter));
+ emitter.userData.group.tick(0);
+};
+
+// ---------------------------- 天空 ----------------------------------------
+
+ComponentMenu.prototype.onAddSky = function () {
+ var obj = new Sky();
+ obj.name = '天空';
+ obj.userData.type = 'Sky';
+ this.app.editor.execute(new AddObjectCommand(obj));
+};
+
+// ---------------------------- 添加火焰 -------------------------------------
+
+ComponentMenu.prototype.onAddFire = function () {
+ var editor = this.app.editor;
+
+ var fire = new Fire(editor.camera);
+
+ editor.execute(new AddObjectCommand(fire));
+
+ fire.userData.fire.update(0);
+};
+
+// -------------------------- 添加水 ---------------------------------------
+
+ComponentMenu.prototype.onAddWater = function () {
+ var editor = this.app.editor;
+
+ var water = new Water(editor.renderer);
+
+ editor.execute(new AddObjectCommand(water));
+
+ this.app.on(`animate.${this.id}`, () => {
+ water.update();
+ });
+};
+
+// ------------------------------ 添加烟 ------------------------------------
+
+ComponentMenu.prototype.onAddSmoke = function () {
+ var editor = this.app.editor;
+ var camera = editor.camera;
+ var renderer = editor.renderer;
+
+ var smoke = new Smoke(camera, renderer);
+
+ smoke.position.y = 3;
+
+ editor.execute(new AddObjectCommand(smoke));
+
+ smoke.update(0);
+};
+
+// --------------------------- 添加刚体 ------------------------------------
+
+ComponentMenu.prototype.addRigidBody = function () {
+ var selected = this.app.editor.selected;
+ if (!selected) {
+ UI.msg('请选择几何体!');
+ return;
+ }
+
+ if (PlysicsUtils.addRigidBodyData(selected) !== false) {
+ this.app.call('objectChanged', this, selected);
+ UI.msg('添加刚体组件成功!');
+ }
+};
+
+// --------------------------- 曲线编辑器 -------------------------------------
+
+ComponentMenu.prototype.curveEditor = function () {
+ UI.msg('待开发');
+};
+
+// --------------------------- 发型编辑器 --------------------------------------
+
+ComponentMenu.prototype.hairEditor = function () {
+ UI.msg('待开发');
+};
+
+// --------------------------- 服装编辑器 --------------------------------------
+
+ComponentMenu.prototype.clothingEditor = function () {
+ UI.msg('待开发');
+};
+
+export default ComponentMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/EditMenu.js b/ShadowEditor.Core/src/editor/menubar/EditMenu.js
new file mode 100644
index 00000000..cfbcbb89
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/EditMenu.js
@@ -0,0 +1,192 @@
+import { UI } from '../../third_party';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import RemoveObjectCommand from '../../command/RemoveObjectCommand';
+
+/**
+ * 编辑菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function EditMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+EditMenu.prototype = Object.create(UI.Control.prototype);
+EditMenu.prototype.constructor = EditMenu;
+
+EditMenu.prototype.render = function () {
+ var _this = this;
+
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '编辑'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ id: 'undo',
+ scope: this.id,
+ html: '撤销(Ctrl+Z)',
+ cls: 'option inactive',
+ onClick: this.undo.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'redo',
+ scope: this.id,
+ html: '重做(Ctrl+Shift+Z)',
+ cls: 'option inactive',
+ onClick: this.redo.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'clearHistory',
+ scope: this.id,
+ html: '清空历史记录',
+ cls: 'option inactive',
+ onClick: this.clearHistory.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ id: 'clone',
+ scope: this.id,
+ html: '复制',
+ cls: 'option inactive',
+ onClick: this.clone.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'delete',
+ scope: this.id,
+ html: '删除(Del)',
+ cls: 'option inactive',
+ onClick: this.delete.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+
+ this.app.on(`historyChanged.${this.id}`, this.onHistoryChanged.bind(this));
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+}
+
+// --------------------- 撤销 --------------------------
+
+EditMenu.prototype.undo = function () {
+ var history = this.app.editor.history;
+ if (history.undos.length === 0) {
+ return;
+ }
+
+ this.app.editor.undo();
+};
+
+// --------------------- 重做 -----------------------------
+
+EditMenu.prototype.redo = function () {
+ var history = this.app.editor.history;
+ if (history.redos.length === 0) {
+ return;
+ }
+
+ this.app.editor.redo();
+};
+
+// -------------------- 清空历史记录 --------------------------------
+
+EditMenu.prototype.clearHistory = function () {
+ var editor = this.app.editor;
+ var history = editor.history;
+
+ if (history.undos.length === 0 && history.redos.length === 0) {
+ return;
+ }
+
+ UI.confirm('询问', '撤销/重做历史记录将被清空。确定吗?', function (event, btn) {
+ if (btn === 'ok') {
+ editor.history.clear();
+ }
+ });
+};
+
+// -------------------------- 复制 -----------------------------------
+
+EditMenu.prototype.clone = function () {
+ var editor = this.app.editor;
+ var object = editor.selected;
+
+ if (object == null || object.parent == null) { // 避免复制场景或相机
+ return;
+ }
+
+ object = object.clone();
+ editor.execute(new AddObjectCommand(object));
+};
+
+// ----------------------- 删除 -----------------------------------
+
+EditMenu.prototype.delete = function () {
+ var editor = this.app.editor;
+ var object = editor.selected;
+
+ if (object == null || object.parent == null) { // 避免删除场景或相机
+ return;
+ }
+
+ UI.confirm('询问', '删除 ' + object.name + '?', function (event, btn) {
+ if (btn === 'ok') {
+ editor.execute(new RemoveObjectCommand(object));
+ }
+ });
+};
+
+// ---------------------- 事件 -----------------------
+
+EditMenu.prototype.onHistoryChanged = function () {
+ var history = this.app.editor.history;
+
+ var undo = UI.get('undo', this.id);
+ var redo = UI.get('redo', this.id);
+ var clearHistory = UI.get('clearHistory', this.id);
+
+ if (history.undos.length === 0) {
+ undo.dom.classList.add('inactive');
+ } else {
+ undo.dom.classList.remove('inactive');
+ }
+
+ if (history.redos.length === 0) {
+ redo.dom.classList.add('inactive');
+ } else {
+ redo.dom.classList.remove('inactive');
+ }
+
+ if (history.undos.length === 0 && history.redos.length === 0) {
+ clearHistory.dom.classList.add('inactive');
+ } else {
+ clearHistory.dom.classList.remove('inactive');
+ }
+};
+
+EditMenu.prototype.onObjectSelected = function () {
+ var editor = this.app.editor;
+
+ var clone = UI.get('clone', this.id);
+ var deleteBtn = UI.get('delete', this.id);
+
+ if (editor.selected && editor.selected.parent != null) {
+ clone.dom.classList.remove('inactive');
+ deleteBtn.dom.classList.remove('inactive');
+ } else {
+ clone.dom.classList.add('inactive');
+ deleteBtn.dom.classList.add('inactive');
+ }
+};
+
+export default EditMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/GeometryMenu.js b/ShadowEditor.Core/src/editor/menubar/GeometryMenu.js
new file mode 100644
index 00000000..9d5a01c5
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/GeometryMenu.js
@@ -0,0 +1,198 @@
+import { UI } from '../../third_party';
+import AddObjectCommand from '../../command/AddObjectCommand';
+
+import Group from '../../object/geometry/Group';
+import Plane from '../../object/geometry/Plane';
+import Box from '../../object/geometry/Box';
+import Circle from '../../object/geometry/Circle';
+import Cylinder from '../../object/geometry/Cylinder';
+import Sphere from '../../object/geometry/Sphere';
+import Icosahedron from '../../object/geometry/Icosahedron';
+import Torus from '../../object/geometry/Torus';
+import TorusKnot from '../../object/geometry/TorusKnot';
+import Teapot from '../../object/geometry/Teapot';
+import Lathe from '../../object/geometry/Lathe';
+import Sprite from '../../object/geometry/Sprite';
+import Text from '../../object/geometry/Text';
+
+/**
+ * 几何体菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function GeometryMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+GeometryMenu.prototype = Object.create(UI.Control.prototype);
+GeometryMenu.prototype.constructor = GeometryMenu;
+
+GeometryMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '几何体'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '组',
+ cls: 'option',
+ onClick: this.addGroup.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '平板',
+ cls: 'option',
+ onClick: this.addPlane.bind(this)
+ }, {
+ xtype: 'div',
+ html: '正方体',
+ cls: 'option',
+ onClick: this.addBox.bind(this)
+ }, {
+ xtype: 'div',
+ html: '圆',
+ cls: 'option',
+ onClick: this.addCircle.bind(this)
+ }, {
+ xtype: 'div',
+ html: '圆柱体',
+ cls: 'option',
+ onClick: this.addCylinder.bind(this)
+ }, {
+ xtype: 'div',
+ html: '球体',
+ cls: 'option',
+ onClick: this.addSphere.bind(this)
+ }, {
+ xtype: 'div',
+ html: '二十面体',
+ cls: 'option',
+ onClick: this.addIcosahedron.bind(this)
+ }, {
+ xtype: 'div',
+ html: '轮胎',
+ cls: 'option',
+ onClick: this.addTorus.bind(this)
+ }, {
+ xtype: 'div',
+ html: '扭结',
+ cls: 'option',
+ onClick: this.addTorusKnot.bind(this)
+ }, {
+ xtype: 'div',
+ html: '茶壶',
+ cls: 'option',
+ onClick: this.addTeaport.bind(this)
+ }, {
+ xtype: 'div',
+ html: '酒杯',
+ cls: 'option',
+ onClick: this.addLathe.bind(this)
+ }, {
+ xtype: 'div',
+ id: 'mAddSprite',
+ html: '精灵',
+ cls: 'option',
+ onClick: this.addSprite.bind(this)
+ }, {
+ xtype: 'div',
+ html: '文本',
+ cls: 'option',
+ onClick: this.addText.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// ------------------------- 组 ---------------------------------
+
+GeometryMenu.prototype.addGroup = function () {
+ this.app.editor.execute(new AddObjectCommand(new Group()));
+};
+
+// ------------------------- 平板 -------------------------------
+
+GeometryMenu.prototype.addPlane = function () {
+ this.app.editor.execute(new AddObjectCommand(new Plane()));
+};
+
+// ------------------------ 正方体 -----------------------------
+
+GeometryMenu.prototype.addBox = function () {
+ this.app.editor.execute(new AddObjectCommand(new Box()));
+};
+
+// ------------------------ 圆 ----------------------------------
+
+GeometryMenu.prototype.addCircle = function () {
+ this.app.editor.execute(new AddObjectCommand(new Circle()));
+};
+
+// ------------------------圆柱体 -------------------------------
+
+GeometryMenu.prototype.addCylinder = function () {
+ this.app.editor.execute(new AddObjectCommand(new Cylinder()));
+};
+
+// ------------------------ 球体 -------------------------------
+
+GeometryMenu.prototype.addSphere = function () {
+ this.app.editor.execute(new AddObjectCommand(new Sphere()));
+};
+
+// ----------------------- 二十面体 -----------------------------
+
+GeometryMenu.prototype.addIcosahedron = function () {
+ this.app.editor.execute(new AddObjectCommand(new Icosahedron()));
+};
+
+// ----------------------- 轮胎 ---------------------------------
+
+GeometryMenu.prototype.addTorus = function () {
+ this.app.editor.execute(new AddObjectCommand(new Torus()));
+};
+
+// ----------------------- 纽结 ---------------------------------
+
+GeometryMenu.prototype.addTorusKnot = function () {
+ this.app.editor.execute(new AddObjectCommand(new TorusKnot()));
+};
+
+// ---------------------- 茶壶 ----------------------------------
+
+GeometryMenu.prototype.addTeaport = function () {
+ this.app.editor.execute(new AddObjectCommand(new Teapot()));
+};
+
+// ---------------------- 酒杯 ----------------------------------
+
+GeometryMenu.prototype.addLathe = function () {
+ this.app.editor.execute(new AddObjectCommand(new Lathe()));
+};
+
+// ---------------------- 精灵 -----------------------------------
+
+GeometryMenu.prototype.addSprite = function () {
+ this.app.editor.execute(new AddObjectCommand(new Sprite()));
+};
+
+// ---------------------- 文本 ----------------------------------
+
+GeometryMenu.prototype.addText = function () {
+ UI.prompt('请输入', null, '一些文字', (event, value) => {
+ this.app.editor.execute(new AddObjectCommand(new Text(value)));
+ });
+};
+
+export default GeometryMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/HelpMenu.js b/ShadowEditor.Core/src/editor/menubar/HelpMenu.js
new file mode 100644
index 00000000..73ca0143
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/HelpMenu.js
@@ -0,0 +1,71 @@
+import { UI } from '../../third_party';
+
+/**
+ * 帮助菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function HelpMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+HelpMenu.prototype = Object.create(UI.Control.prototype);
+HelpMenu.prototype.constructor = HelpMenu;
+
+HelpMenu.prototype.render = function () {
+ var _this = this;
+
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '帮助'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ cls: 'option',
+ html: '源码',
+ onClick: () => {
+ window.open('https://github.com/tengge1/ShadowEditor', '_blank');
+ }
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '示例',
+ onClick: () => {
+ window.open('https://github.com/tengge1/ShadowEditor-examples', '_blank');
+ }
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '文档',
+ onClick: () => {
+ window.open('https://tengge1.github.io/ShadowEditor/', '_blank');
+ }
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '关于',
+ onClick: () => {
+ UI.alert(
+ `About`,
+ `Name: ShadowEditor
+ Author: tengge
+ License: MIT
+ Thanks to three.js and everyone who helped us.`
+ );
+ }
+ }]
+ }]
+ });
+
+ container.render();
+};
+
+export default HelpMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/LightMenu.js b/ShadowEditor.Core/src/editor/menubar/LightMenu.js
new file mode 100644
index 00000000..4dcf0459
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/LightMenu.js
@@ -0,0 +1,177 @@
+import { UI } from '../../third_party';
+import AddObjectCommand from '../../command/AddObjectCommand';
+
+import PointLight from '../../object/light/PointLight';
+import HemisphereLight from '../../object/light/HemisphereLight';
+import RectAreaLight from '../../object/light/RectAreaLight';
+
+/**
+ * 光源菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function LightMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+LightMenu.prototype = Object.create(UI.Control.prototype);
+LightMenu.prototype.constructor = LightMenu;
+
+LightMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '光源'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '环境光',
+ cls: 'option',
+ onClick: this.addAmbientLight.bind(this)
+ }, {
+ xtype: 'div',
+ html: '平行光',
+ cls: 'option',
+ onClick: this.addDirectionalLight.bind(this)
+ }, {
+ xtype: 'div',
+ html: '点光源',
+ cls: 'option',
+ onClick: this.addPointLight.bind(this)
+ }, {
+ xtype: 'div',
+ html: '聚光灯',
+ cls: 'option',
+ onClick: this.addSpotLight.bind(this)
+ }, {
+ xtype: 'div',
+ html: '半球光',
+ cls: 'option',
+ onClick: this.addHemisphereLight.bind(this)
+ }, {
+ xtype: 'div',
+ html: '矩形光',
+ cls: 'option',
+ onClick: this.addRectAreaLight.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// ------------------------- 环境光 ------------------------------
+
+LightMenu.prototype.addAmbientLight = function () {
+ var editor = this.app.editor;
+
+ var color = 0xaaaaaa;
+
+ var light = new THREE.AmbientLight(color);
+ light.name = '环境光';
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+// ------------------------- 平行光 ------------------------------
+
+LightMenu.prototype.addDirectionalLight = function () {
+ var editor = this.app.editor;
+
+ var color = 0xffffff;
+ var intensity = 1;
+
+ var light = new THREE.DirectionalLight(color, intensity);
+ light.name = '平行光';
+ light.castShadow = true;
+ light.shadow.mapSize.x = 2048;
+ light.shadow.mapSize.y = 2048;
+ light.shadow.camera.left = -100;
+ light.shadow.camera.right = 100;
+ light.shadow.camera.top = 100;
+ light.shadow.camera.bottom = -100;
+ light.position.set(5, 10, 7.5);
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+// ------------------------- 点光源 ------------------------------
+
+LightMenu.prototype.addPointLight = function () {
+ var editor = this.app.editor;
+
+ var color = 0xffffff;
+ var intensity = 1;
+ var distance = 0;
+
+ var light = new PointLight(color, intensity, distance);
+ light.name = '点光源';
+ light.position.y = 5;
+ light.castShadow = true;
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+// ------------------------- 聚光灯 ------------------------------
+
+LightMenu.prototype.addSpotLight = function () {
+ var editor = this.app.editor;
+
+ var color = 0xffffff;
+ var intensity = 1;
+ var distance = 0;
+ var angle = Math.PI * 0.1;
+ var penumbra = 0;
+
+ var light = new THREE.SpotLight(color, intensity, distance, angle, penumbra);
+
+ light.name = '聚光灯';
+ light.castShadow = true;
+
+ light.position.set(5, 10, 7.5);
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+// ------------------------- 半球光 ------------------------------
+
+LightMenu.prototype.addHemisphereLight = function () {
+ var editor = this.app.editor;
+ var skyColor = 0x00aaff;
+ var groundColor = 0xffaa00;
+ var intensity = 1;
+
+ var light = new HemisphereLight(skyColor, groundColor, intensity);
+ light.name = '半球光';
+
+ light.position.set(0, 10, 0);
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+// ------------------------- 矩形光 ------------------------------
+
+LightMenu.prototype.addRectAreaLight = function () {
+ var editor = this.app.editor;
+
+ var color = 0xffffff;
+ var intensity = 1;
+ var width = 20;
+ var height = 10;
+
+ var light = new RectAreaLight(color, intensity, width, height);
+ light.name = '矩形光';
+
+ light.position.set(0, 6, 0);
+
+ editor.execute(new AddObjectCommand(light));
+};
+
+export default LightMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/Logo.js b/ShadowEditor.Core/src/editor/menubar/Logo.js
new file mode 100644
index 00000000..ee2ebd50
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/Logo.js
@@ -0,0 +1,29 @@
+import { UI } from '../../third_party';
+
+/**
+ * Logo标志
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function Logo(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+Logo.prototype = Object.create(UI.Control.prototype);
+Logo.prototype.constructor = Logo;
+
+Logo.prototype.render = function () {
+ var _this = this;
+
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'logo',
+ html: ''
+ });
+
+ container.render();
+}
+
+export default Logo;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/Menubar.js b/ShadowEditor.Core/src/editor/menubar/Menubar.js
new file mode 100644
index 00000000..a753665c
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/Menubar.js
@@ -0,0 +1,62 @@
+import { UI } from '../../third_party';
+import Logo from './Logo';
+import SceneMenu from './SceneMenu';
+import EditMenu from './EditMenu';
+import GeometryMenu from './GeometryMenu';
+import LightMenu from './LightMenu';
+import AssetMenu from './AssetMenu';
+import TerrainMenu from './TerrainMenu';
+import PhysicsMenu from './PhysicsMenu';
+import ComponentMenu from './ComponentMenu';
+import PlayMenu from './PlayMenu';
+import OptionsMenu from './OptionsMenu';
+import HelpMenu from './HelpMenu';
+import StatusMenu from './StatusMenu';
+
+/**
+ * 菜单栏
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Menubar(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+Menubar.prototype = Object.create(UI.Control.prototype);
+Menubar.prototype.constructor = Menubar;
+
+Menubar.prototype.render = function () {
+ var params = { app: this.app };
+
+ var container = UI.create({
+ xtype: 'div',
+ id: 'menubar',
+ cls: 'menubar',
+ parent: this.parent,
+ children: [
+ // Logo
+ new Logo(params),
+
+ // 左侧
+ new SceneMenu(params),
+ new EditMenu(params),
+ new GeometryMenu(params),
+ new LightMenu(params),
+ new AssetMenu(params),
+ new TerrainMenu(params),
+ new PhysicsMenu(params),
+ new ComponentMenu(params),
+ new PlayMenu(params),
+ new OptionsMenu(params),
+ new HelpMenu(params),
+
+ // 右侧
+ new StatusMenu(params)
+ ]
+ });
+
+ container.render();
+};
+
+export default Menubar;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/OptionsMenu.js b/ShadowEditor.Core/src/editor/menubar/OptionsMenu.js
new file mode 100644
index 00000000..17f016a5
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/OptionsMenu.js
@@ -0,0 +1,84 @@
+import { UI } from '../../third_party';
+import OptionsWindow from '../window/OptionsWindow';
+
+/**
+ * 选项菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function OptionsMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+OptionsMenu.prototype = Object.create(UI.Control.prototype);
+OptionsMenu.prototype.constructor = OptionsMenu;
+
+OptionsMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '选项'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ cls: 'option',
+ html: '外观',
+ onClick: this.onSurfaceOptions.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '场景',
+ onClick: this.onSceneOptions.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '渲染器',
+ onClick: this.onRendererOptions.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// ---------------------------------- 外观选项 ---------------------------------------
+
+OptionsMenu.prototype.onSurfaceOptions = function () {
+ if (this.optionsWindow === undefined) {
+ this.optionsWindow = new OptionsWindow({ app: this.app });
+ this.optionsWindow.render();
+ }
+ this.optionsWindow.show();
+ this.optionsWindow.changeTab('外观');
+};
+
+// ---------------------------------- 场景选项 ---------------------------------------
+
+OptionsMenu.prototype.onSceneOptions = function () {
+ if (this.optionsWindow === undefined) {
+ this.optionsWindow = new OptionsWindow({ app: this.app });
+ this.optionsWindow.render();
+ }
+ this.optionsWindow.show();
+ this.optionsWindow.changeTab('场景');
+};
+
+// ---------------------------------- 渲染器选项 -------------------------------------
+
+OptionsMenu.prototype.onRendererOptions = function () {
+ if (this.optionsWindow === undefined) {
+ this.optionsWindow = new OptionsWindow({ app: this.app });
+ this.optionsWindow.render();
+ }
+ this.optionsWindow.show();
+ this.optionsWindow.changeTab('渲染器');
+};
+
+export default OptionsMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/PhysicsMenu.js b/ShadowEditor.Core/src/editor/menubar/PhysicsMenu.js
new file mode 100644
index 00000000..ae191e1b
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/PhysicsMenu.js
@@ -0,0 +1,68 @@
+import { UI } from '../../third_party';
+
+/**
+ * 物体菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function PhysicsMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+PhysicsMenu.prototype = Object.create(UI.Control.prototype);
+PhysicsMenu.prototype.constructor = PhysicsMenu;
+
+PhysicsMenu.prototype.render = function () {
+ var _this = this;
+
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '物理'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '添加平板',
+ cls: 'option',
+ onClick: function () {
+ _this.app.call('mAddPhysicsPlane', _this);
+ }
+ }, {
+ xtype: 'div',
+ html: '添加墙',
+ cls: 'option',
+ onClick: function () {
+ _this.app.call('mAddPhysicsWall', _this);
+ }
+ }, {
+ xtype: 'div',
+ html: '添加布料',
+ cls: 'option',
+ onClick: function () {
+ _this.app.call('mAddPhysicsCloth', _this);
+ }
+ }, {
+ xtype: 'div',
+ id: 'mThrowBall',
+ html: '开启探测小球',
+ cls: 'option',
+ onClick: function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ _this.app.call('mThrowBall', _this);
+ }
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+export default PhysicsMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/PlayMenu.js b/ShadowEditor.Core/src/editor/menubar/PlayMenu.js
new file mode 100644
index 00000000..a0d659aa
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/PlayMenu.js
@@ -0,0 +1,56 @@
+import { UI } from '../../third_party';
+
+/**
+ * 启动菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function PlayMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.isPlaying = false;
+}
+
+PlayMenu.prototype = Object.create(UI.Control.prototype);
+PlayMenu.prototype.constructor = PlayMenu;
+
+PlayMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ id: 'mPlay',
+ xtype: 'div',
+ cls: 'title',
+ html: '启动',
+ onClick: this.onTogglePlay.bind(this)
+ }]
+ });
+
+ container.render();
+}
+
+PlayMenu.prototype.onTogglePlay = function () {
+ var editor = this.app.editor;
+
+ if (this.isPlaying === false) {
+ this.isPlaying = true;
+ UI.get('mPlay').dom.innerHTML = '停止';
+ this.startPlayer();
+ } else {
+ this.isPlaying = false;
+ UI.get('mPlay').dom.innerHTML = '启动';
+ this.stopPlayer();
+ }
+};
+
+PlayMenu.prototype.startPlayer = function () { // 启动播放器
+ this.app.player.start();
+};
+
+PlayMenu.prototype.stopPlayer = function () { // 停止播放器
+ this.app.player.stop();
+};
+
+export default PlayMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/SceneMenu.js b/ShadowEditor.Core/src/editor/menubar/SceneMenu.js
new file mode 100644
index 00000000..04f234f3
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/SceneMenu.js
@@ -0,0 +1,245 @@
+import { UI } from '../../third_party';
+import SceneWindow from '../window/SceneWindow';
+import Converter from '../../serialization/Converter';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * 场景菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SceneMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+SceneMenu.prototype = Object.create(UI.Control.prototype);
+SceneMenu.prototype.constructor = SceneMenu;
+
+SceneMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '场景'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ html: '新建',
+ cls: 'option',
+ onClick: this.newScene.bind(this)
+ }, {
+ xtype: 'div',
+ html: '载入',
+ cls: 'option',
+ onClick: this.loadScene.bind(this)
+ }, {
+ xtype: 'div',
+ html: '保存',
+ cls: 'option',
+ onClick: this.saveScene.bind(this)
+ }, {
+ xtype: 'div',
+ html: '另存为',
+ cls: 'option',
+ onClick: this.saveAsScene.bind(this)
+ }, {
+ xtype: 'hr'
+ }, {
+ xtype: 'div',
+ html: '发布静态网站',
+ cls: 'option',
+ onClick: this.publish.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+
+ // this.link = document.createElement('a');
+ // this.link.style.display = 'none';
+ // document.body.appendChild(this.link); // Firefox workaround, see #6594
+}
+
+// ---------------------------- 新建场景 ---------------------------------
+
+SceneMenu.prototype.newScene = function () {
+ var editor = this.app.editor;
+
+ if (editor.sceneName == null) {
+ editor.clear();
+ document.title = '未命名';
+ return;
+ }
+
+ UI.confirm('询问', '所有未保存数据将丢失,确定要新建场景吗?', function (event, btn) {
+ if (btn === 'ok') {
+ editor.clear();
+ editor.sceneID = null;
+ editor.sceneName = null;
+ document.title = '未命名';
+ }
+ });
+};
+
+// --------------------------- 载入场景 --------------------------------------
+
+SceneMenu.prototype.loadScene = function () {
+ if (this.window == null) {
+ this.window = new SceneWindow({ app: this.app });
+ this.window.render();
+ }
+ this.window.show();
+};
+
+// --------------------------- 保存场景 ----------------------------------------
+
+SceneMenu.prototype.saveScene = function () { // 保存场景
+ var editor = this.app.editor;
+ var sceneName = editor.sceneName;
+
+ if (sceneName == null) {
+ UI.prompt('保存场景', '名称', '新场景', (event, name) => {
+ this.commitSave(name);
+ });
+ } else {
+ this.commitSave(sceneName);
+ }
+};
+
+SceneMenu.prototype.commitSave = function (sceneName) {
+ var editor = this.app.editor;
+
+ var obj = (new Converter()).toJSON({
+ options: this.app.options,
+ camera: editor.camera,
+ renderer: editor.renderer,
+ scripts: editor.scripts,
+ animation: editor.animation,
+ scene: editor.scene
+ });
+
+ Ajax.post(`${this.app.options.server}/api/Scene/Save`, {
+ Name: sceneName,
+ Data: JSON.stringify(obj)
+ }, function (result) {
+ var obj = JSON.parse(result);
+
+ if (obj.Code === 200) {
+ editor.sceneID = obj.ID;
+ editor.sceneName = sceneName;
+ document.title = sceneName;
+ }
+
+ UI.msg(obj.Msg);
+ });
+};
+
+// --------------------------- 另存为场景 -------------------------------------
+
+SceneMenu.prototype.saveAsScene = function () {
+ var sceneName = this.app.editor.sceneName;
+
+ if (sceneName == null) {
+ sceneName = '新场景';
+ }
+
+ UI.prompt('保存场景', '名称', sceneName, (event, name) => {
+ this.app.editor.sceneName = name;
+ document.title = name;
+ this.commitSaveAs(name);
+ });
+};
+
+SceneMenu.prototype.commitSaveAs = function (sceneName) {
+ var editor = this.app.editor;
+
+ var obj = (new Converter()).toJSON({
+ options: this.app.options,
+ camera: editor.camera,
+ renderer: editor.renderer,
+ scripts: editor.scripts,
+ animation: editor.animation,
+ scene: editor.scene
+ });
+
+ Ajax.post(`${this.app.options.server}/api/Scene/Save`, {
+ Name: sceneName,
+ Data: JSON.stringify(obj)
+ }, function (result) {
+ var obj = JSON.parse(result);
+
+ if (obj.Code === 200) {
+ editor.sceneID = obj.ID;
+ editor.sceneName = sceneName;
+ document.title = sceneName;
+ }
+
+ UI.msg(obj.Msg);
+ });
+};
+
+// ------------------------- 发布静态网站 ------------------------------
+
+SceneMenu.prototype.publish = function () {
+ UI.confirm('发布网站', '是否把所有场景、资源发布为静态网站?', (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${this.app.options.server}/api/Publish/Publish`, function (result) {
+ var obj = JSON.parse(result);
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+// ------------------------ 本地打包发布 ---------------------------------------
+
+// SceneMenu.prototype.publishSceneLocal = function () {
+// var editor = this.app.editor;
+
+// var zip = new JSZip();
+// //
+
+// var obj = (new Converter()).toJSON({
+// options: this.app.options,
+// camera: this.app.editor.camera,
+// renderer: this.app.editor.renderer,
+// scripts: this.app.editor.scripts,
+// scene: this.app.editor.scene
+// });
+
+// var output = JSON.stringify(obj);
+
+// zip.file('scene.json', output);
+
+// // 保存数据
+
+// var manager = new THREE.LoadingManager(() => {
+// this.savePublishScene(zip.generate({
+// type: 'blob'
+// }), `${editor.sceneName}.zip`);
+// });
+
+// var loader = new THREE.FileLoader(manager);
+// loader.load('index.html', content => {
+// zip.file('index.html', content);
+// });
+// loader.load('dist/ShadowEditor.js', function (content) {
+// zip.file('dist/ShadowEditor.js', content);
+// });
+// };
+
+// SceneMenu.prototype.savePublishScene = function (text, filename) {
+// var blob = new Blob([text], { type: 'text/plain' });
+
+// this.link.href = URL.createObjectURL(blob);
+// this.link.download = filename;
+// this.link.click();
+// };
+
+export default SceneMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/StatusMenu.js b/ShadowEditor.Core/src/editor/menubar/StatusMenu.js
new file mode 100644
index 00000000..d706d39a
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/StatusMenu.js
@@ -0,0 +1,36 @@
+import { UI } from '../../third_party';
+
+/**
+ * 状态菜单(菜单栏右侧)
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function StatusMenu(options) {
+ UI.Control.call(this, options);
+ options = options || {};
+
+ this.app = options.app;
+}
+
+StatusMenu.prototype = Object.create(UI.Control.prototype);
+StatusMenu.prototype.constructor = StatusMenu;
+
+StatusMenu.prototype.render = function () {
+ var _this = this;
+
+ var container = UI.create({
+ xtype: 'div',
+ id: 'mStatus',
+ parent: this.parent,
+ cls: 'menu right',
+ children: [{
+ xtype: 'text',
+ text: 'r' + THREE.REVISION,
+ cls: 'title version'
+ }]
+ });
+
+ container.render();
+}
+
+export default StatusMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/menubar/TerrainMenu.js b/ShadowEditor.Core/src/editor/menubar/TerrainMenu.js
new file mode 100644
index 00000000..81fdf45a
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/menubar/TerrainMenu.js
@@ -0,0 +1,124 @@
+import { UI } from '../../third_party';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import PerlinTerrain from '../../object/terrain/PerlinTerrain';
+import ShaderTerrain from '../../object/terrain/ShaderTerrain';
+import PhysicsTerrain from '../../object/terrain/PhysicsTerrain';
+
+/**
+ * 地形菜单
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TerrainMenu(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+TerrainMenu.prototype = Object.create(UI.Control.prototype);
+TerrainMenu.prototype.constructor = TerrainMenu;
+
+TerrainMenu.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'menu',
+ children: [{
+ xtype: 'div',
+ cls: 'title',
+ html: '地形'
+ }, {
+ xtype: 'div',
+ cls: 'options',
+ children: [{
+ xtype: 'div',
+ cls: 'option',
+ html: '柏林地形',
+ onClick: this.createPerlinTerrain.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '创建着色器地形',
+ onClick: this.createShaderTerrain.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '创建物理地形',
+ onClick: this.createPhysicsTerrain.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '升高地形',
+ onClick: this.raiseTerrain.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '降低地形',
+ onClick: this.reduceTerrain.bind(this)
+ }, {
+ xtype: 'div',
+ cls: 'option',
+ html: '批量种树',
+ onClick: this.plantTrees.bind(this)
+ }]
+ }]
+ });
+
+ container.render();
+}
+
+// ---------------------------- 创建地形 -----------------------------------
+
+TerrainMenu.prototype.createPerlinTerrain = function () {
+ this.app.editor.execute(new AddObjectCommand(new PerlinTerrain()));
+};
+
+// ---------------------------- 创建着色器地形 ----------------------------------------
+
+TerrainMenu.prototype.createShaderTerrain = function () {
+ var dom = this.app.viewport.container.dom;
+
+ var terrain = new ShaderTerrain(this.app.editor.renderer, dom.clientWidth, dom.clientHeight);
+
+ this.app.editor.execute(new AddObjectCommand(terrain));
+
+ terrain.update(0);
+
+ // this.app.on(`animate.Terrain2`, (clock, deltaTime) => {
+ // terrain.update(deltaTime);
+ // });
+};
+
+// ----------------------------- 创建物理地形 ---------------------------------
+
+TerrainMenu.prototype.createPhysicsTerrain = function () {
+ var terrain = new PhysicsTerrain();
+
+ this.app.editor.execute(new AddObjectCommand(terrain));
+
+ this.app.physics.world.addRigidBody(terrain.userData.physicsBody);
+ this.app.physics.rigidBodies.push(terrain);
+
+ // this.app.on(`animate.${this.id}`, (clock, deltaTime) => {
+ // terrain.update(deltaTime, this.app.physics.world);
+ // });
+};
+
+// ---------------------------- 升高地形 -----------------------------------
+
+TerrainMenu.prototype.raiseTerrain = function () {
+
+};
+
+// ---------------------------- 降低地形 ------------------------------------
+
+TerrainMenu.prototype.reduceTerrain = function () {
+
+};
+
+// ----------------------------- 批量种树 --------------------------------------
+
+TerrainMenu.prototype.plantTrees = function () {
+
+};
+
+export default TerrainMenu;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/ScriptEditor.js b/ShadowEditor.Core/src/editor/script/ScriptEditor.js
new file mode 100644
index 00000000..a07da95d
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/ScriptEditor.js
@@ -0,0 +1,369 @@
+import { UI } from '../../third_party';
+import SetScriptValueCommand from '../../command/SetScriptValueCommand';
+
+/**
+ * 脚本编辑器
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function ScriptEditor(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+
+ this.codemirror = null;
+ this.server = null;
+ this.delay = null; // 代码校验延迟函数
+ this.delayTime = 1000; // 代码校验间隔时间(毫秒)
+
+ this.uuid = null;
+ this.name = null;
+ this.mode = null;
+ this.source = null;
+ this.title = null;
+
+ this.errorLines = []; // 代码错误行数
+ this.widgets = [];
+
+ this.callback = null;
+};
+
+ScriptEditor.prototype = Object.create(UI.Control.prototype);
+ScriptEditor.prototype.constructor = ScriptEditor;
+
+ScriptEditor.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ id: 'scriptEditor',
+ cls: 'script',
+ style: {
+ backgroundColor: '#272822',
+ display: 'none'
+ },
+ children: [{
+ xtype: 'div',
+ style: {
+ padding: '10px'
+ },
+ children: [{
+ xtype: 'text',
+ id: 'scriptTitle',
+ style: {
+ color: '#fff'
+ }
+ }, {
+ xtype: 'closebutton',
+ style: {
+ position: 'absolute',
+ top: '3px',
+ right: '1px',
+ cursor: 'pointer'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ };
+
+ var container = UI.create(data);
+ container.render();
+
+ // 绑定事件
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+};
+
+/**
+ * 打开脚本文件
+ * @param {*} uuid 脚本uuid
+ * @param {*} name 名称
+ * @param {*} mode 类型 javascript、vertexShader、fragmentShader、json 默认:javascript
+ * @param {*} source 源码 文件初始代码 默认:空
+ * @param {*} title 标题 文件标题 默认:未命名.${文件类型}
+ * @param {*} callback 回调函数
+ */
+ScriptEditor.prototype.open = function (uuid, name, mode, source, title, callback) {
+ var scriptTitle = UI.get('scriptTitle');
+
+ // 连续打开脚本时,自动保存上次打开的文件
+ if (this.uuid != null) {
+ this.save();
+ this.uuid = null;
+ this.name = null;
+ this.mode = null;
+ this.source = null;
+ this.title = null;
+ }
+
+ // 打开新文件
+ name = name || '未命名';
+ mode = mode || 'javascript';
+ source = source || '';
+ title = title || '未命名';
+ title = `${title}.${(mode === 'vertexShader' || mode === 'fragmentShader') ? '.glsl' : (mode === 'json' ? '.json' : '.js')}`;
+
+ this.uuid = uuid;
+ this.name = name;
+ this.mode = mode;
+ this.source = source;
+ this.title = title;
+ this.callback = callback;
+
+ this.show();
+
+ scriptTitle.setValue(title);
+
+ this.codemirror.setValue(source);
+
+ // 设置codemirror模式
+ if (mode === 'json') {
+ this.codemirror.setOption('mode', {
+ name: 'javascript',
+ json: true
+ });
+ } if (mode === 'vertexShader' || mode === 'fragmentShader') {
+ this.codemirror.setOption('mode', 'glsl');
+ } else {
+ this.codemirror.setOption('mode', mode);
+ }
+ this.codemirror.focus();
+ this.codemirror.setCursor({
+ line: 0,
+ ch: 0
+ });
+};
+
+/**
+ * 显示脚本编辑器
+ */
+ScriptEditor.prototype.show = function () {
+ var container = UI.get('scriptEditor');
+
+ container.dom.style.display = 'block';
+};
+
+/**
+ * 隐藏脚本编辑器
+ */
+ScriptEditor.prototype.hide = function () {
+ var container = UI.get('scriptEditor');
+
+ this.save();
+ container.dom.style.display = 'none';
+
+ this.uuid = null;
+ this.name = null;
+ this.mode = null;
+ this.source = null;
+ this.title = null;
+};
+
+/**
+ * 保存脚本
+ */
+ScriptEditor.prototype.save = function () {
+ var value = this.codemirror.getValue();
+
+ if (typeof (this.callback) === 'function') {
+ this.callback.call(this, value);
+ }
+
+ this.app.log(`${this.uuid}脚本保存成功!`);
+};
+
+/**
+ * 刷新脚本编辑器
+ * @param {*} title 标题
+ * @param {*} source 代码
+ * @param {*} cursorPosition 光标位置
+ * @param {*} scrollInfo 滚动信息
+ */
+ScriptEditor.prototype.refresh = function (title, source, cursorPosition, scrollInfo) {
+ var container = UI.get('scriptEditor');
+ var title = UI.get('scriptTitle');
+
+ // 复制codemirror的历史记录,因为"codemirror.setValue(...)"函数会改变它的历史。
+ var history = this.codemirror.getHistory();
+ title.setValue(title);
+ this.codemirror.setValue(source);
+
+ if (cursorPosition !== undefined) {
+ this.codemirror.setCursor(cursorPosition);
+ this.codemirror.scrollTo(scrollInfo.left, scrollInfo.top);
+ }
+
+ this.codemirror.setHistory(history); // 设置历史到先前状态
+};
+
+// 内部方法
+
+ScriptEditor.prototype.onAppStarted = function () {
+ var container = UI.get('scriptEditor');
+
+ var codemirror = CodeMirror(container.dom, {
+ value: '',
+ lineNumbers: true,
+ matchBrackets: true,
+ indentWithTabs: true,
+ tabSize: 4,
+ indentUnit: 4,
+ hintOptions: {
+ completeSingle: false
+ }
+ });
+
+ codemirror.setOption('theme', 'monokai');
+ codemirror.on('change', this.onCodeMirrorChange.bind(this));
+
+ // 防止回退键删除物体
+ var wrapper = codemirror.getWrapperElement();
+ wrapper.addEventListener('keydown', event => {
+ event.stopPropagation();
+ });
+
+ // tern js 自动完成
+ var server = new CodeMirror.TernServer({
+ caseInsensitive: true,
+ plugins: { threejs: null }
+ });
+
+ // 快捷键
+ codemirror.setOption('extraKeys', {
+ 'Ctrl-Space': cm => { server.complete(cm); },
+ 'Ctrl-I': cm => { server.showType(cm); },
+ 'Ctrl-O': cm => { server.showDocs(cm); },
+ 'Alt-.': cm => { server.jumpToDef(cm); },
+ 'Alt-,': cm => { server.jumpBack(cm); },
+ 'Ctrl-Q': cm => { server.rename(cm); },
+ 'Ctrl-.': cm => { server.selectName(cm); }
+ });
+
+ codemirror.on('cursorActivity', cm => {
+ if (this.mode !== 'javascript') {
+ return;
+ }
+ server.updateArgHints(cm);
+ });
+
+ codemirror.on('keypress', (cm, kb) => {
+ if (this.mode !== 'javascript') {
+ return;
+ }
+ var typed = String.fromCharCode(kb.which || kb.keyCode);
+ if (/[\w\.]/.exec(typed)) {
+ server.complete(cm);
+ }
+ });
+
+ this.codemirror = codemirror;
+ this.server = server;
+};
+
+/**
+ * 代码修改事件
+ */
+ScriptEditor.prototype.onCodeMirrorChange = function () {
+ if (this.codemirror.state.focused === false) {
+ return;
+ }
+
+ if (this.delay) {
+ clearTimeout(this.delay);
+ }
+
+ this.delay = setTimeout(() => {
+ var code = this.codemirror.getValue();
+ this.validate(code);
+ }, this.delayTime);
+};
+
+/**
+ * 校验编辑器中代码正确性
+ * @param {*} string
+ */
+ScriptEditor.prototype.validate = function (string) {
+ var codemirror = this.codemirror;
+ var mode = this.mode;
+
+ var errorLines = this.errorLines;
+ var widgets = this.widgets;
+
+ var errors = [];
+
+ return codemirror.operation(() => {
+ while (errorLines.length > 0) {
+ codemirror.removeLineClass(errorLines.shift(), 'background', 'errorLine');
+ }
+
+ while (widgets.length > 0) {
+ codemirror.removeLineWidget(widgets.shift());
+ }
+
+ switch (mode) {
+ case 'javascript':
+ try {
+ var syntax = esprima.parse(string, { tolerant: true });
+ errors = syntax.errors;
+ } catch (error) {
+ errors.push({
+ lineNumber: error.lineNumber - 1,
+ message: error.message
+ });
+ }
+
+ for (var i = 0; i < errors.length; i++) {
+ var error = errors[i];
+ error.message = error.message.replace(/Line [0-9]+: /, '');
+ }
+ break;
+ case 'json':
+ jsonlint.parseError = (message, info) => {
+ message = message.split('\n')[3];
+ errors.push({
+ lineNumber: info.loc.first_line - 1,
+ message: message
+ });
+ };
+
+ try {
+ jsonlint.parse(string);
+ } catch (error) {
+ // ignore failed error recovery
+ }
+ break;
+ case 'vertexShader':
+ case 'fragmentShader':
+ try {
+ var shaderType = mode === 'vertexShader' ? glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
+ glslprep.parseGlsl(string, shaderType);
+ } catch (error) {
+ if (error instanceof glslprep.SyntaxError) {
+ errors.push({
+ lineNumber: error.line,
+ message: "Syntax Error: " + error.message
+ });
+ } else {
+ console.error(error.stack || error);
+ }
+ }
+ }
+
+ for (var i = 0; i < errors.length; i++) {
+ var error = errors[i];
+
+ var message = document.createElement('div');
+ message.className = 'esprima-error';
+ message.textContent = error.message;
+
+ var lineNumber = Math.max(error.lineNumber, 0);
+ errorLines.push(lineNumber);
+
+ codemirror.addLineClass(lineNumber, 'background', 'errorLine');
+
+ var widget = codemirror.addLineWidget(lineNumber, message);
+ widgets.push(widget);
+ }
+
+ return errors.length === 0;
+ });
+};
+
+export default ScriptEditor;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/code/FragmentShaderStarter.js b/ShadowEditor.Core/src/editor/script/code/FragmentShaderStarter.js
new file mode 100644
index 00000000..46a7cfe1
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/code/FragmentShaderStarter.js
@@ -0,0 +1,9 @@
+var FragmentShaderStarter = `
+precision mediump float;
+
+void main() {
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+`;
+
+export default FragmentShaderStarter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/code/JavaScriptStarter.js b/ShadowEditor.Core/src/editor/script/code/JavaScriptStarter.js
new file mode 100644
index 00000000..316f8656
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/code/JavaScriptStarter.js
@@ -0,0 +1,68 @@
+var JavaScriptStarter = `
+// 场景渲染前执行一次
+function init() {
+
+}
+
+// 场景渲染后执行一次
+function start() {
+
+}
+
+// 程序运行过程中,每帧都要执行
+function update(clock, deltaTime) {
+
+}
+
+// 程序结束运行后执行一次
+function stop() {
+
+}
+
+// 监听鼠标点击事件
+function onClick(event) {
+
+}
+
+// 监听鼠标双击事件
+function onDblClick(event) {
+
+}
+
+// 监听键盘按下事件
+function onKeyDown(event) {
+
+}
+
+// 监听键盘抬起事件
+function onKeyUp(event) {
+
+}
+
+// 监听鼠标按下事件
+function onMouseDown(event) {
+
+}
+
+// 监听鼠标移动事件
+function onMouseMove(event) {
+
+}
+
+// 监听鼠标抬起事件
+function onMouseUp(event) {
+
+}
+
+// 监听鼠标滚轮事件
+function onMouseWheel(event) {
+
+}
+
+// 监听屏幕大小改变事件
+function onResize(event) {
+
+}
+`;
+
+export default JavaScriptStarter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/code/JsonStarter.js b/ShadowEditor.Core/src/editor/script/code/JsonStarter.js
new file mode 100644
index 00000000..f84a997e
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/code/JsonStarter.js
@@ -0,0 +1,15 @@
+var JsonStarter = `
+{
+ "defines": {
+
+ },
+ "uniforms": {
+
+ },
+ "attributes": {
+
+ }
+}
+`;
+
+export default JsonStarter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/code/VertexShaderStarter.js b/ShadowEditor.Core/src/editor/script/code/VertexShaderStarter.js
new file mode 100644
index 00000000..3857a311
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/code/VertexShaderStarter.js
@@ -0,0 +1,14 @@
+var VertexShaderStarter = `
+precision mediump float;
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+attribute vec3 position;
+
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+export default VertexShaderStarter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/script/window/ScriptWindow.js b/ShadowEditor.Core/src/editor/script/window/ScriptWindow.js
new file mode 100644
index 00000000..30bfe306
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/script/window/ScriptWindow.js
@@ -0,0 +1,153 @@
+import { UI } from '../../../third_party';
+import JavaScriptStarter from '../code/JavaScriptStarter';
+import VertexShaderStarter from '../code/VertexShaderStarter';
+import FragmentShaderStarter from '../code/FragmentShaderStarter';
+import JsonStarter from '../code/JsonStarter';
+
+/**
+ * 脚本创建窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ScriptWindow(options) {
+ UI.Control.call(this, options);
+ options = options || {};
+
+ this.app = options.app;
+}
+
+ScriptWindow.prototype = Object.create(UI.Control.prototype);
+ScriptWindow.prototype.constructor = ScriptWindow;
+
+ScriptWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'scriptWindow',
+ scope: this.id,
+ parent: this.app.container,
+ title: '创建脚本',
+ width: '350px',
+ height: '220px',
+ bodyStyle: {
+ paddingTop: '32px'
+ },
+ shade: false,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'scriptName',
+ scope: this.id,
+ text: '未命名'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'select',
+ id: 'scriptType',
+ scope: this.id,
+ options: {
+ 'javascript': 'JavaScript',
+ 'vertexShader': '顶点着色器',
+ 'fragmentShader': '片源着色器',
+ 'json': '着色器程序信息'
+ },
+ value: 'javascript',
+ disabled: true
+ }]
+ }],
+ buttons: [{
+ xtype: 'button',
+ text: '确定',
+ onClick: this.onCreateScript.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ onClick: this.onCancelScript.bind(this)
+ }]
+ });
+
+ container.render();
+};
+
+ScriptWindow.prototype.show = function () {
+ var container = UI.get('scriptWindow', this.id);
+ container.show();
+};
+
+ScriptWindow.prototype.hide = function () {
+ var container = UI.get('scriptWindow', this.id);
+ container.hide();
+};
+
+ScriptWindow.prototype.reset = function () {
+ var scriptName = UI.get('scriptName', this.id);
+ var scriptType = UI.get('scriptType', this.id);
+
+ scriptName.setValue('未命名');
+ scriptType.setValue('javascript');
+};
+
+ScriptWindow.prototype.onCreateScript = function () {
+ var scriptName = UI.get('scriptName', this.id).getValue();
+ var scriptType = UI.get('scriptType', this.id).getValue();
+
+ // 判断脚本名称是否重复
+ var scripts = Object.values(this.app.editor.scripts);
+ if (scripts.filter(n => n.name === scriptName).length > 0) {
+ UI.msg('脚本名称重复!');
+ return;
+ }
+
+ this.hide();
+
+ var initCode;
+
+ switch (scriptType) {
+ case 'javascript':
+ initCode = JavaScriptStarter;
+ break;
+ case 'vertexShader':
+ initCode = VertexShaderStarter;
+ break;
+ case 'fragmentShader':
+ initCode = FragmentShaderStarter;
+ break;
+ case 'json':
+ initCode = JsonStarter;
+ break;
+ default:
+ initCode = JavaScriptStarter;
+ break;
+ }
+
+ var uuid = THREE.Math.generateUUID();
+
+ this.app.script.open(uuid, scriptName, scriptType, initCode, scriptName, source => {
+ var script = this.app.editor.scripts[uuid];
+ script.source = source;
+ });
+
+ this.app.editor.scripts[uuid] = {
+ id: 0,
+ name: scriptName,
+ type: scriptType,
+ source: initCode,
+ uuid: uuid
+ };
+
+ this.app.call('scriptChanged', this);
+};
+
+ScriptWindow.prototype.onCancelScript = function () {
+ var container = UI.get('scriptWindow', this.id);
+ container.hide();
+};
+
+export default ScriptWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar/AnimationPanel.js b/ShadowEditor.Core/src/editor/sidebar/AnimationPanel.js
new file mode 100644
index 00000000..5c2dedf0
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar/AnimationPanel.js
@@ -0,0 +1,30 @@
+import { UI } from '../../third_party';
+import BasicAnimationComponent from '../../component/animation/BasicAnimationComponent';
+import TweenAnimationComponent from '../../component/animation/TweenAnimationComponent';
+
+/**
+ * 动画面板
+ */
+function AnimationPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+AnimationPanel.prototype = Object.create(UI.Control.prototype);
+AnimationPanel.prototype.constructor = AnimationPanel;
+
+AnimationPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ children: [
+ new BasicAnimationComponent({ app: this.app }),
+ new TweenAnimationComponent({ app: this.app }),
+ ]
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default AnimationPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar/HistoryPanel.js b/ShadowEditor.Core/src/editor/sidebar/HistoryPanel.js
new file mode 100644
index 00000000..c8a4415a
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar/HistoryPanel.js
@@ -0,0 +1,91 @@
+import { UI } from '../../third_party';
+
+/**
+ * 历史记录面板
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+function HistoryPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+HistoryPanel.prototype = Object.create(UI.Control.prototype);
+HistoryPanel.prototype.constructor = HistoryPanel;
+
+HistoryPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ children: [{
+ xtype: 'label',
+ text: '历史记录'
+ }, {
+ xtype: 'br'
+ }, {
+ xtype: 'br'
+ }, {
+ xtype: 'outliner',
+ id: 'historyOutlinear',
+ onChange: this.onChange.bind(this)
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+ this.app.on(`editorCleared.${this.id}`, this.refreshUI.bind(this));
+ this.app.on(`historyChanged.${this.id}`, this.refreshUI.bind(this));
+};
+
+HistoryPanel.prototype.onAppStarted = function () {
+ var outliner = UI.get('historyOutlinear');
+ outliner.editor = this.app.editor;
+ this.refreshUI();
+};
+
+HistoryPanel.prototype.refreshUI = function () {
+ var history = this.app.editor.history;
+ var outliner = UI.get('historyOutlinear');
+
+ var options = [];
+
+ function buildOption(object) {
+ var option = document.createElement('div');
+ option.value = object.id;
+ return option;
+ }
+
+ (function addObjects(objects) {
+ for (var i = 0, l = objects.length; i < l; i++) {
+ var object = objects[i];
+ var option = buildOption(object);
+ option.innerHTML = ' ' + object.name;
+ options.push(option);
+ }
+ })(history.undos);
+
+
+ (function addObjects(objects, pad) {
+ for (var i = objects.length - 1; i >= 0; i--) {
+ var object = objects[i];
+ var option = buildOption(object);
+ option.innerHTML = ' ' + object.name;
+ option.style.opacity = 0.3;
+ options.push(option);
+ }
+ })(history.redos, ' ');
+
+ outliner.setOptions(options);
+};
+
+HistoryPanel.prototype.onChange = function () {
+ var history = this.app.editor.history;
+ var outliner = UI.get('historyOutlinear');
+
+ history.goToState(parseInt(outliner.getValue()));
+};
+
+export default HistoryPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar/PropertyPanel.js b/ShadowEditor.Core/src/editor/sidebar/PropertyPanel.js
new file mode 100644
index 00000000..c0772134
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar/PropertyPanel.js
@@ -0,0 +1,68 @@
+import { UI } from '../../third_party';
+import BasicComponent from '../../component/BasicComponent';
+import TransformComponent from '../../component/TransformComponent';
+import CameraComponent from '../../component/CameraComponent';
+import LightComponent from '../../component/LightComponent';
+import ShadowComponent from '../../component/ShadowComponent';
+import GeometryComponent from '../../component/GeometryComponent';
+import MaterialComponent from '../../component/MaterialComponent';
+import PhysicsWorldComponent from '../../component/physics/PhysicsWorldComponent';
+import AudioListenerComponent from '../../component/audio/AudioListenerComponent';
+import ParticleEmitterComponent from '../../component/ParticleEmitterComponent';
+import SceneComponent from '../../component/SceneComponent';
+import BackgroundMusicComponent from '../../component/audio/BackgroundMusicComponent';
+import FireComponent from '../../component/FireComponent';
+import SmokeComponent from '../../component/SmokeComponent';
+import ReflectorComponent from '../../component/ReflectorComponent';
+import LMeshComponent from '../../component/LMeshComponent';
+import MMDComponent from '../../component/MMDComponent';
+import RigidBodyComponent from '../../component/physics/RigidBodyComponent';
+import SkyComponent from '../../component/object/SkyComponent';
+import PerlinTerrainComponent from '../../component/object/PerlinTerrainComponent';
+
+/**
+ * 属性面板
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function PropertyPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+PropertyPanel.prototype = Object.create(UI.Control.prototype);
+PropertyPanel.prototype.constructor = PropertyPanel;
+
+PropertyPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ children: [
+ new BasicComponent({ app: this.app }),
+ new TransformComponent({ app: this.app }),
+ new SceneComponent({ app: this.app }),
+ new CameraComponent({ app: this.app }),
+ new LightComponent({ app: this.app }),
+ new ShadowComponent({ app: this.app }),
+ new ReflectorComponent({ app: this.app }),
+ new SkyComponent({ app: this.app }),
+ new PerlinTerrainComponent({ app: this.app }),
+ new AudioListenerComponent({ app: this.app }),
+ new BackgroundMusicComponent({ app: this.app }),
+ new PhysicsWorldComponent({ app: this.app }),
+ new ParticleEmitterComponent({ app: this.app }),
+ new FireComponent({ app: this.app }),
+ new SmokeComponent({ app: this.app }),
+ new LMeshComponent({ app: this.app }),
+ new MMDComponent({ app: this.app }),
+ new RigidBodyComponent({ app: this.app }),
+ new GeometryComponent({ app: this.app }),
+ new MaterialComponent({ app: this.app })
+ ]
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default PropertyPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar/SettingPanel.js b/ShadowEditor.Core/src/editor/sidebar/SettingPanel.js
new file mode 100644
index 00000000..acd7b52a
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar/SettingPanel.js
@@ -0,0 +1,208 @@
+import { UI } from '../../third_party';
+
+/**
+ * 设置面板
+ * @author tengge / https://github.com/tengge1
+ */
+function SettingPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+SettingPanel.prototype = Object.create(UI.Control.prototype);
+SettingPanel.prototype.constructor = SettingPanel;
+
+SettingPanel.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ borderTop: 0,
+ paddingTop: '20px'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ style: {
+ color: '#555',
+ fontWeight: 'bold'
+ },
+ text: '帮助器'
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '网格'
+ }, {
+ xtype: 'boolean',
+ id: 'showGrid',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '相机'
+ }, {
+ xtype: 'boolean',
+ id: 'showCamera',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '点光源'
+ }, {
+ xtype: 'boolean',
+ id: 'showPointLight',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '平行光'
+ }, {
+ xtype: 'boolean',
+ id: 'showDirectionalLight',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '聚光灯'
+ }, {
+ xtype: 'boolean',
+ id: 'showSpotLight',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '半球光'
+ }, {
+ xtype: 'boolean',
+ id: 'showHemisphereLight',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '矩形光'
+ }, {
+ xtype: 'boolean',
+ id: 'showRectAreaLight',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '骨骼'
+ }, {
+ xtype: 'boolean',
+ id: 'showSkeleton',
+ scope: this.id,
+ onChange: this.update.bind(this)
+ }]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`tabSelected.${this.id}`, this.onTabSelected.bind(this));
+};
+
+SettingPanel.prototype.onTabSelected = function (tabName) {
+ if (tabName !== 'setting') {
+ return;
+ }
+
+ // 帮助器
+ var showGrid = UI.get('showGrid', this.id);
+ showGrid.setValue(this.app.options.showGrid);
+
+ var showCamera = UI.get('showCamera', this.id);
+ showCamera.setValue(this.app.options.showCameraHelper);
+
+ var showPointLight = UI.get('showPointLight', this.id);
+ showPointLight.setValue(this.app.options.showPointLightHelper);
+
+ var showDirectionalLight = UI.get('showDirectionalLight', this.id);
+ showDirectionalLight.setValue(this.app.options.showDirectionalLightHelper);
+
+ var showSpotLight = UI.get('showSpotLight', this.id);
+ showSpotLight.setValue(this.app.options.showSpotLightHelper);
+
+ var showHemisphereLight = UI.get('showHemisphereLight', this.id);
+ showHemisphereLight.setValue(this.app.options.showHemisphereLightHelper);
+
+ var showRectAreaLight = UI.get('showRectAreaLight', this.id);
+ showRectAreaLight.setValue(this.app.options.showRectAreaLightHelper);
+
+ var showSkeleton = UI.get('showSkeleton', this.id);
+ showSkeleton.setValue(this.app.options.showSkeletonHelper);
+};
+
+SettingPanel.prototype.update = function () {
+ // 帮助器
+ var showGrid = UI.get('showGrid', this.id).getValue();
+ this.app.options.showGrid = showGrid;
+ this.app.editor.grid.visible = showGrid;
+
+ var showCamera = UI.get('showCamera', this.id).getValue();
+ this.app.options.showCameraHelper = showCamera;
+
+ var showPointLight = UI.get('showPointLight', this.id).getValue();
+ this.app.options.showPointLightHelper = showPointLight;
+
+ var showDirectionalLight = UI.get('showDirectionalLight', this.id).getValue();
+ this.app.options.showDirectionalLightHelper = showDirectionalLight;
+
+ var showSpotLight = UI.get('showSpotLight', this.id).getValue();
+ this.app.options.showSpotLightHelper = showSpotLight;
+
+ var showHemisphereLight = UI.get('showHemisphereLight', this.id).getValue();
+ this.app.options.showHemisphereLightHelper = showHemisphereLight;
+
+ var showRectAreaLight = UI.get('showRectAreaLight', this.id).getValue();
+ this.app.options.showRectAreaLightHelper = showRectAreaLight;
+
+ var showSkeleton = UI.get('showSkeleton', this.id).getValue();
+ this.app.options.showSkeletonHelper = showSkeleton;
+
+ Object.values(this.app.editor.helpers).forEach(n => {
+ if (n instanceof THREE.CameraHelper) {
+ n.visible = showCamera;
+ } else if (n instanceof THREE.PointLightHelper) {
+ n.visible = showPointLight;
+ } else if (n instanceof THREE.DirectionalLightHelper) {
+ n.visible = showDirectionalLight;
+ } else if (n instanceof THREE.SpotLightHelper) {
+ n.visible = showSpotLight;
+ } else if (n instanceof THREE.HemisphereLightHelper) {
+ n.visible = showHemisphereLight;
+ } else if (n instanceof THREE.RectAreaLightHelper) {
+ n.visible = showRectAreaLight;
+ } else if (n instanceof THREE.SkeletonHelper) {
+ n.visible = showSkeleton;
+ }
+ });
+};
+
+export default SettingPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar/Sidebar.js b/ShadowEditor.Core/src/editor/sidebar/Sidebar.js
new file mode 100644
index 00000000..10399a00
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar/Sidebar.js
@@ -0,0 +1,159 @@
+import { UI } from '../../third_party';
+import PropertyPanel from './PropertyPanel';
+import AnimationPanel from './AnimationPanel';
+import SettingPanel from './SettingPanel';
+import HistoryPanel from './HistoryPanel';
+
+/**
+ * 侧边栏
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Sidebar(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+Sidebar.prototype = Object.create(UI.Control.prototype);
+Sidebar.prototype.constructor = Sidebar;
+
+Sidebar.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'sidebar',
+ children: [{
+ xtype: 'div',
+ cls: 'tabs',
+ style: {
+ position: 'sticky',
+ top: 0,
+ zIndex: 10
+ },
+ children: [{
+ xtype: 'text',
+ id: 'propertyTab',
+ scope: this.id,
+ text: '属性',
+ onClick: () => {
+ this.app.call('tabSelected', this, 'property');
+ }
+ }, {
+ xtype: 'text',
+ id: 'animationTab',
+ scope: this.id,
+ text: '动画',
+ onClick: () => {
+ this.app.call('tabSelected', this, 'animation');
+ }
+ }, {
+ xtype: 'text',
+ id: 'settingTab',
+ scope: this.id,
+ text: '设置',
+ onClick: () => {
+ this.app.call('tabSelected', this, 'setting');
+ }
+ }, {
+ xtype: 'text',
+ id: 'historyTab',
+ scope: this.id,
+ text: '历史',
+ onClick: () => {
+ this.app.call('tabSelected', this, 'history');
+ }
+ }]
+ }, {
+ xtype: 'div',
+ id: 'propertyPanel',
+ scope: this.id,
+ children: [
+ new PropertyPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'animationPanel',
+ scope: this.id,
+ children: [
+ new AnimationPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'settingPanel',
+ scope: this.id,
+ children: [
+ new SettingPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ id: 'historyPanel',
+ scope: this.id,
+ children: [
+ new HistoryPanel({ app: this.app })
+ ]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+ this.app.on(`tabSelected.${this.id}`, this.onTabSelected.bind(this));
+};
+
+Sidebar.prototype.onAppStarted = function () {
+ this.app.call('tabSelected', this, 'property');
+};
+
+Sidebar.prototype.onTabSelected = function (tabName) {
+ var tabNames = [
+ 'property',
+ 'animation',
+ 'setting',
+ 'history'
+ ];
+ if (tabNames.indexOf(tabName) === -1) {
+ return;
+ }
+
+ var propertyTab = UI.get('propertyTab', this.id);
+ var animationTab = UI.get('animationTab', this.id);
+ var settingTab = UI.get('settingTab', this.id);
+ var historyTab = UI.get('historyTab', this.id);
+
+ var propertyPanel = UI.get('propertyPanel', this.id);
+ var animationPanel = UI.get('animationPanel', this.id);
+ var settingPanel = UI.get('settingPanel', this.id);
+ var historyPanel = UI.get('historyPanel', this.id);
+
+ propertyTab.dom.className = '';
+ animationTab.dom.className = '';
+ settingTab.dom.className = '';
+ historyTab.dom.className = '';
+
+ propertyPanel.dom.style.display = 'none';
+ animationPanel.dom.style.display = 'none';
+ settingPanel.dom.style.display = 'none';
+ historyPanel.dom.style.display = 'none';
+
+ switch (tabName) {
+ case 'property':
+ propertyTab.dom.className = 'selected';
+ propertyPanel.dom.style.display = '';
+ break;
+ case 'animation':
+ animationTab.dom.className = 'selected';
+ animationPanel.dom.style.display = '';
+ break;
+ case 'setting':
+ settingTab.dom.className = 'selected';
+ settingPanel.dom.style.display = '';
+ break;
+ case 'history':
+ historyTab.dom.className = 'selected';
+ historyPanel.dom.style.display = '';
+ break;
+ }
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar2/HierachyPanel.js b/ShadowEditor.Core/src/editor/sidebar2/HierachyPanel.js
new file mode 100644
index 00000000..cd25e65e
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar2/HierachyPanel.js
@@ -0,0 +1,161 @@
+import { UI } from '../../third_party';
+
+/**
+ * 场景层次图面板
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function HierachyPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+HierachyPanel.prototype = Object.create(UI.Control.prototype);
+HierachyPanel.prototype.constructor = HierachyPanel;
+
+HierachyPanel.prototype.render = function () {
+ var editor = this.app.editor;
+
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel',
+ style: {
+ paddingTop: '10px'
+ },
+ children: [{
+ xtype: 'outliner',
+ id: 'outliner',
+ editor: editor,
+ onChange: this.onChange.bind(this),
+ onDblClick: this.onDblClick.bind(this)
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
+ this.app.on(`editorCleared.${this.id}`, this.refreshUI.bind(this));
+ this.app.on(`sceneGraphChanged.${this.id}`, this.refreshUI.bind(this));
+ this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
+ this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
+};
+
+HierachyPanel.prototype.onChange = function () {
+ var editor = this.app.editor;
+ var outliner = UI.get('outliner');
+
+ editor.selectById(parseInt(outliner.getValue()));
+};
+
+HierachyPanel.prototype.onDblClick = function () {
+ var editor = this.app.editor;
+ var outliner = UI.get('outliner');
+
+ editor.focusById(parseInt(outliner.getValue()));
+};
+
+HierachyPanel.prototype.onAppStarted = function () {
+ this.refreshUI();
+};
+
+/**
+ * 场景物体改变
+ * @param {*} object
+ */
+HierachyPanel.prototype.onObjectChanged = function (object) {
+ var outliner = UI.get('outliner');
+
+ var options = outliner.options;
+
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+
+ if (option.value === object.id) {
+ option.innerHTML = this.buildHTML(object);
+ return;
+ }
+ }
+};
+
+/**
+ * 选中物体改变
+ * @param {*} object
+ */
+HierachyPanel.prototype.onObjectSelected = function (object) {
+ var outliner = UI.get('outliner');
+ outliner.setValue(object !== null ? object.id : null);
+};
+
+// outliner
+HierachyPanel.prototype.buildOption = function (object, draggable) {
+ var option = document.createElement('div');
+ option.draggable = draggable;
+ option.innerHTML = this.buildHTML(object);
+ option.value = object.id;
+ return option;
+};
+
+HierachyPanel.prototype.buildHTML = function (object) {
+ var html = ' ' + object.name;
+
+ if (object instanceof THREE.Mesh) {
+ var geometry = object.geometry;
+ var material = object.material;
+
+ html += ' ' + geometry.name;
+ html += ' ' + (material.name == null ? '' : material.name);
+ }
+
+ html += this.getScript(object.uuid);
+ return html;
+};
+
+HierachyPanel.prototype.getScript = function (uuid) {
+ var editor = this.app.editor;
+
+ if (editor.scripts[uuid] !== undefined) {
+ return ' ';
+ }
+
+ return '';
+};
+
+HierachyPanel.prototype.refreshUI = function () {
+ var editor = this.app.editor;
+ var camera = editor.camera;
+ var scene = editor.scene;
+ var outliner = UI.get('outliner');
+
+ if (outliner.editor === undefined) {
+ outliner.editor = editor;
+ }
+
+ var options = [];
+
+ options.push(this.buildOption(camera, false));
+ options.push(this.buildOption(scene, false));
+
+ var _this = this;
+
+ (function addObjects(objects, pad) {
+ for (var i = 0, l = objects.length; i < l; i++) {
+ var object = objects[i];
+
+ var option = _this.buildOption(object, true);
+ option.style.paddingLeft = (pad * 10) + 'px';
+ options.push(option);
+
+ addObjects(object.children, pad + 1);
+ }
+ })(scene.children, 1);
+
+ outliner.setOptions(options);
+
+ if (editor.selected !== null) {
+ outliner.setValue(editor.selected.id);
+ }
+};
+
+export default HierachyPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar2/ScriptPanel.js b/ShadowEditor.Core/src/editor/sidebar2/ScriptPanel.js
new file mode 100644
index 00000000..b13929fb
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar2/ScriptPanel.js
@@ -0,0 +1,151 @@
+import { UI } from '../../third_party';
+import AddScriptCommand from '../../command/AddScriptCommand';
+import ScriptWindow from '../script/window/ScriptWindow';
+
+/**
+ * 脚本面板
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function ScriptPanel(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+ScriptPanel.prototype = Object.create(UI.Control.prototype);
+ScriptPanel.prototype.constructor = ScriptPanel;
+
+ScriptPanel.prototype.render = function () {
+ var editor = this.app.editor;
+
+ var data = {
+ xtype: 'div',
+ parent: this.parent,
+ cls: 'Panel scriptPanel',
+ children: [{
+ xtype: 'row',
+ id: 'scriptsContainer'
+ }, {
+ xtype: 'button',
+ id: 'newCustomScript',
+ text: '新建脚本',
+ onClick: this.createNewScript.bind(this)
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+
+ this.app.on(`scriptChanged.${this.id}`, this.update.bind(this));
+};
+
+ScriptPanel.prototype.createNewScript = function () {
+ if (this.window == null) {
+ this.window = new ScriptWindow({
+ app: this.app
+ });
+ this.window.render();
+ }
+ this.window.reset();
+ this.window.show();
+};
+
+ScriptPanel.prototype.update = function () {
+ var container = UI.get('scriptsContainer');
+ container.dom.innerHTML = '';
+ container.dom.style.display = 'none';
+
+ var scripts = this.app.editor.scripts;
+
+ if (Object.keys(scripts).length === 0) {
+ return;
+ }
+
+ container.dom.style.display = 'block';
+
+ Object.keys(scripts).forEach(n => {
+ var script = scripts[n];
+ var uuid = script.uuid;
+ var name = script.name;
+ var extension;
+
+ switch (script.type) {
+ case 'javascript':
+ extension = '.js';
+ break;
+ case 'vertexShader':
+ case 'fragmentShader':
+ extension = '.glsl';
+ break;
+ case 'json':
+ extension = '.json';
+ break;
+ }
+
+ var data = {
+ xtype: 'container',
+ parent: container.dom,
+ children: [{
+ xtype: 'text',
+ text: name + extension,
+ style: {
+ width: '100px',
+ fontSize: '12px'
+ }
+ }, {
+ xtype: 'button',
+ text: '编辑',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: () => {
+ this.editScript(uuid);
+ }
+ }, {
+ xtype: 'button',
+ text: '删除',
+ style: {
+ marginLeft: '4px'
+ },
+ onClick: () => {
+ this.deleteScript(uuid);
+ }
+ }, {
+ xtype: 'br'
+ }]
+ };
+
+ UI.create(data).render();
+ });
+};
+
+/**
+ * 编辑脚本
+ * @param {*} uuid
+ */
+ScriptPanel.prototype.editScript = function (uuid) {
+ var script = this.app.editor.scripts[uuid];
+ if (script) {
+ this.app.script.open(uuid, script.name, script.type, script.source, script.name, source => {
+ script.source = source;
+ });
+ }
+};
+
+/**
+ * 删除脚本
+ * @param {*} uuid
+ */
+ScriptPanel.prototype.deleteScript = function (uuid) {
+ var script = this.app.editor.scripts[uuid];
+
+ UI.confirm('询问', `是否删除脚本${script.name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ delete this.app.editor.scripts[uuid];
+ this.app.call('scriptChanged', this);
+ UI.msg(`${script.name}删除成功!`);
+ }
+ });
+};
+
+export default ScriptPanel;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/sidebar2/Sidebar.js b/ShadowEditor.Core/src/editor/sidebar2/Sidebar.js
new file mode 100644
index 00000000..8ee46d51
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/sidebar2/Sidebar.js
@@ -0,0 +1,56 @@
+import { UI } from '../../third_party';
+import HierachyPanel from './HierachyPanel';
+import ScriptPanel from './ScriptPanel';
+
+/**
+ * 侧边栏2
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Sidebar(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+};
+
+Sidebar.prototype = Object.create(UI.Control.prototype);
+Sidebar.prototype.constructor = Sidebar;
+
+Sidebar.prototype.render = function () {
+ var data = {
+ xtype: 'div',
+ cls: 'sidebar',
+ parent: this.parent,
+ children: [{
+ xtype: 'div',
+ cls: 'tabs',
+ children: [{
+ xtype: 'text',
+ text: '场景',
+ cls: 'selected'
+ }]
+ }, { // 场景面板
+ xtype: 'div',
+ children: [
+ new HierachyPanel({ app: this.app })
+ ]
+ }, {
+ xtype: 'div',
+ cls: 'tabs',
+ children: [{
+ xtype: 'text',
+ text: '脚本',
+ cls: 'selected'
+ }]
+ }, {
+ xtype: 'div',
+ children: [
+ new ScriptPanel({ app: this.app })
+ ]
+ }]
+ };
+
+ var control = UI.create(data);
+ control.render();
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/AudioEditWindow.js b/ShadowEditor.Core/src/editor/window/AudioEditWindow.js
new file mode 100644
index 00000000..84e008d7
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/AudioEditWindow.js
@@ -0,0 +1,110 @@
+import { UI } from '../../third_party';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * 音频编辑窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function AudioEditWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.callback = options.callback || null;
+}
+
+AudioEditWindow.prototype = Object.create(UI.Control.prototype);
+AudioEditWindow.prototype.constructor = AudioEditWindow;
+
+AudioEditWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: '编辑音频',
+ width: '320px',
+ height: '280px',
+ shade: true,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ style: {
+ justifyContent: 'center',
+ marginTop: '8px'
+ },
+ children: [{
+ xtype: 'button',
+ text: '确定',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.save.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ });
+ container.render();
+};
+
+AudioEditWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+};
+
+AudioEditWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+AudioEditWindow.prototype.setData = function (data) {
+ this.data = data;
+ this.updateUI();
+};
+
+AudioEditWindow.prototype.updateUI = function () {
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ name.setValue(this.data.Name);
+};
+
+AudioEditWindow.prototype.save = function () {
+ var server = this.app.options.server;
+
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+
+ Ajax.post(`${server}/api/Audio/Edit`, {
+ ID: this.data.ID,
+ Name: name.getValue()
+ }, json => {
+ var obj = JSON.parse(json);
+ UI.msg(obj.Msg);
+ if (obj.Code === 200) {
+ this.hide();
+ if (typeof (this.callback) === 'function') {
+ this.callback(obj);
+ }
+ }
+ });
+};
+
+export default AudioEditWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/AudioWindow.js b/ShadowEditor.Core/src/editor/window/AudioWindow.js
new file mode 100644
index 00000000..27b2a8c9
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/AudioWindow.js
@@ -0,0 +1,80 @@
+import { UI } from '../../third_party';
+import ImageListWindow from '../../ui/ImageListWindow';
+import Ajax from '../../utils/Ajax';
+import AudioEditWindow from './AudioEditWindow';
+
+/**
+ * 纹理窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function AudioWindow(options) {
+ ImageListWindow.call(this, options);
+ this.app = options.app;
+
+ this.title = '音频列表';
+ this.imageIcon = 'icon-audio';
+ // this.cornerTextField = 'Type';
+ this.uploadUrl = `${this.app.options.server}/api/Audio/Add`;
+ this.preImageUrl = this.app.options.server;
+ this.showUploadButton = true;
+
+ this.onSelect = options.onSelect || null;
+}
+
+AudioWindow.prototype = Object.create(ImageListWindow.prototype);
+AudioWindow.prototype.constructor = AudioWindow;
+
+AudioWindow.prototype.beforeUpdateList = function () {
+ var server = this.app.options.server;
+
+ return new Promise(resolve => {
+ Ajax.getJson(`${server}/api/Audio/List`, obj => {
+ resolve(obj.Data);
+ });
+ });
+};
+
+AudioWindow.prototype.onUpload = function (obj) {
+ this.update();
+ UI.msg(obj.Msg);
+};
+
+AudioWindow.prototype.onClick = function (data) {
+ if (typeof (this.onSelect) === 'function') {
+ this.onSelect(data);
+ } else {
+ UI.msg('请在音频相关控件中修改音乐。');
+ }
+};
+
+AudioWindow.prototype.onEdit = function (data) {
+ if (this.editWindow === undefined) {
+ this.editWindow = new AudioEditWindow({
+ app: this.app,
+ parent: this.parent,
+ callback: this.update.bind(this)
+ });
+ this.editWindow.render();
+ }
+ this.editWindow.setData(data);
+ this.editWindow.show();
+};
+
+AudioWindow.prototype.onDelete = function (data) {
+ var server = this.app.options.server;
+
+ UI.confirm('询问', `是否删除音频${data.Name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${server}/api/Audio/Delete?ID=${data.ID}`, json => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.update();
+ }
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+export default AudioWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/MMDEditWindow.js b/ShadowEditor.Core/src/editor/window/MMDEditWindow.js
new file mode 100644
index 00000000..1b355ee5
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/MMDEditWindow.js
@@ -0,0 +1,125 @@
+import { UI } from '../../third_party';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * MMD编辑窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function MMDEditWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.callback = options.callback || null;
+}
+
+MMDEditWindow.prototype = Object.create(UI.Control.prototype);
+MMDEditWindow.prototype.constructor = MMDEditWindow;
+
+MMDEditWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: '编辑MMD',
+ width: '320px',
+ height: '280px',
+ shade: true,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '缩略图'
+ }, {
+ xtype: 'imageuploader',
+ id: 'image',
+ scope: this.id,
+ server: this.app.options.server
+ }]
+ }, {
+ xtype: 'row',
+ style: {
+ justifyContent: 'center',
+ marginTop: '8px'
+ },
+ children: [{
+ xtype: 'button',
+ text: '确定',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.save.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ });
+ container.render();
+};
+
+MMDEditWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+};
+
+MMDEditWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+MMDEditWindow.prototype.setData = function (data) {
+ this.data = data;
+ this.updateUI();
+};
+
+MMDEditWindow.prototype.updateUI = function () {
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+ name.setValue(this.data.Name);
+ image.setValue(this.data.Thumbnail);
+};
+
+MMDEditWindow.prototype.save = function () {
+ var server = this.app.options.server;
+
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+
+ Ajax.post(`${server}/api/MMD/Edit`, {
+ ID: this.data.ID,
+ Name: name.getValue(),
+ Image: image.getValue()
+ }, json => {
+ var obj = JSON.parse(json);
+ UI.msg(obj.Msg);
+ if (obj.Code === 200) {
+ this.hide();
+ if (typeof (this.callback) === 'function') {
+ this.callback(obj);
+ }
+ }
+ });
+};
+
+export default MMDEditWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/MMDWindow.js b/ShadowEditor.Core/src/editor/window/MMDWindow.js
new file mode 100644
index 00000000..410ded14
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/MMDWindow.js
@@ -0,0 +1,118 @@
+import { UI } from '../../third_party';
+import ImageListWindow from '../../ui/ImageListWindow';
+import Ajax from '../../utils/Ajax';
+import ModelLoader from '../../loader/ModelLoader';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import MMDEditWindow from './MMDEditWindow';
+
+/**
+ * MMD窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function MMDWindow(options) {
+ ImageListWindow.call(this, options);
+ this.app = options.app;
+
+ this.title = 'MMD资源列表';
+ this.imageIcon = 'icon-model';
+ this.cornerTextField = 'Type';
+ this.uploadUrl = `${this.app.options.server}/api/MMD/Add`;
+ this.preImageUrl = this.app.options.server;
+ this.showUploadButton = true;
+
+ this.onSelect = options.onSelect || null;
+}
+
+MMDWindow.prototype = Object.create(ImageListWindow.prototype);
+MMDWindow.prototype.constructor = MMDWindow;
+
+MMDWindow.prototype.beforeUpdateList = function () {
+ var server = this.app.options.server;
+
+ return new Promise(resolve => {
+ Ajax.getJson(`${server}/api/MMD/List`, obj => {
+ resolve(obj.Data);
+ });
+ });
+};
+
+MMDWindow.prototype.onUpload = function (obj) {
+ this.update();
+ UI.msg(obj.Msg);
+};
+
+MMDWindow.prototype.onClick = function (model) {
+ if (model.Type === 'vmd') {
+ if (this.onSelect) {
+ this.onSelect(model);
+ } else {
+ UI.msg(`无法将动画文件添加到场景。`);
+ }
+ return;
+ }
+
+ var loader = new ModelLoader(this.app);
+
+ var url = model.Url;
+
+ if (model.Url.indexOf(';') > -1) { // 包含多个入口文件
+ url = url.split(';').map(n => this.app.options.server + n);
+ } else {
+ url = this.app.options.server + model.Url;
+ }
+
+ loader.load(url, model).then(obj => {
+ if (!obj) {
+ return;
+ }
+ obj.name = model.Name;
+
+ Object.assign(obj.userData, model, {
+ Server: true
+ });
+
+ var cmd = new AddObjectCommand(obj);
+ cmd.execute();
+
+ if (obj.userData.scripts) {
+ obj.userData.scripts.forEach(n => {
+ this.app.editor.scripts[n.uuid] = n;
+ });
+ this.app.call('scriptChanged', this);
+ }
+
+ this.hide();
+ });
+};
+
+MMDWindow.prototype.onEdit = function (data) {
+ if (this.editWindow === undefined) {
+ this.editWindow = new MMDEditWindow({
+ app: this.app,
+ parent: this.parent,
+ callback: this.update.bind(this)
+ });
+ this.editWindow.render();
+ }
+ this.editWindow.setData(data);
+ this.editWindow.show();
+};
+
+MMDWindow.prototype.onDelete = function (data) {
+ var server = this.app.options.server;
+
+ UI.confirm('询问', `是否删除MMD资源${data.Name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${server}/api/MMD/Delete?ID=${data.ID}`, json => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.update();
+ }
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+export default MMDWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/ModelEditWindow.js b/ShadowEditor.Core/src/editor/window/ModelEditWindow.js
new file mode 100644
index 00000000..5707bb09
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/ModelEditWindow.js
@@ -0,0 +1,125 @@
+import { UI } from '../../third_party';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * 模型编辑窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ModelEditWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.callback = options.callback || null;
+}
+
+ModelEditWindow.prototype = Object.create(UI.Control.prototype);
+ModelEditWindow.prototype.constructor = ModelEditWindow;
+
+ModelEditWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: '编辑模型',
+ width: '320px',
+ height: '280px',
+ shade: true,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '缩略图'
+ }, {
+ xtype: 'imageuploader',
+ id: 'image',
+ scope: this.id,
+ server: this.app.options.server
+ }]
+ }, {
+ xtype: 'row',
+ style: {
+ justifyContent: 'center',
+ marginTop: '8px'
+ },
+ children: [{
+ xtype: 'button',
+ text: '确定',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.save.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ });
+ container.render();
+};
+
+ModelEditWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+};
+
+ModelEditWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+ModelEditWindow.prototype.setData = function (data) {
+ this.data = data;
+ this.updateUI();
+};
+
+ModelEditWindow.prototype.updateUI = function () {
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+ name.setValue(this.data.Name);
+ image.setValue(this.data.Thumbnail);
+};
+
+ModelEditWindow.prototype.save = function () {
+ var server = this.app.options.server;
+
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+
+ Ajax.post(`${server}/api/Mesh/Edit`, {
+ ID: this.data.ID,
+ Name: name.getValue(),
+ Image: image.getValue()
+ }, json => {
+ var obj = JSON.parse(json);
+ UI.msg(obj.Msg);
+ if (obj.Code === 200) {
+ this.hide();
+ if (typeof (this.callback) === 'function') {
+ this.callback(obj);
+ }
+ }
+ });
+};
+
+export default ModelEditWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/ModelWindow.js b/ShadowEditor.Core/src/editor/window/ModelWindow.js
new file mode 100644
index 00000000..2077fdd5
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/ModelWindow.js
@@ -0,0 +1,105 @@
+import { UI } from '../../third_party';
+import ImageListWindow from '../../ui/ImageListWindow';
+import Ajax from '../../utils/Ajax';
+import ModelLoader from '../../loader/ModelLoader';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import ModelEditWindow from './ModelEditWindow';
+
+/**
+ * 模型窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ModelWindow(options) {
+ ImageListWindow.call(this, options);
+ this.app = options.app;
+
+ this.title = '模型列表';
+ this.imageIcon = 'icon-model';
+ this.cornerTextField = 'Type';
+ this.uploadUrl = `${this.app.options.server}/api/Mesh/Add`;
+ this.preImageUrl = this.app.options.server;
+ this.showUploadButton = true;
+}
+
+ModelWindow.prototype = Object.create(ImageListWindow.prototype);
+ModelWindow.prototype.constructor = ModelWindow;
+
+ModelWindow.prototype.beforeUpdateList = function () {
+ var server = this.app.options.server;
+
+ return new Promise(resolve => {
+ Ajax.getJson(`${server}/api/Mesh/List`, obj => {
+ resolve(obj.Data);
+ });
+ });
+};
+
+ModelWindow.prototype.onUpload = function (obj) {
+ this.update();
+ UI.msg(obj.Msg);
+};
+
+ModelWindow.prototype.onClick = function (model) {
+ var loader = new ModelLoader(this.app);
+
+ var url = model.Url;
+
+ if (model.Url.indexOf(';') > -1) { // 包含多个入口文件
+ url = url.split(';').map(n => this.app.options.server + n);
+ } else {
+ url = this.app.options.server + model.Url;
+ }
+
+ loader.load(url, model).then(obj => {
+ if (!obj) {
+ return;
+ }
+ obj.name = model.Name;
+
+ Object.assign(obj.userData, model, {
+ Server: true
+ });
+
+ var cmd = new AddObjectCommand(obj);
+ cmd.execute();
+
+ if (obj.userData.scripts) {
+ obj.userData.scripts.forEach(n => {
+ this.app.editor.scripts[n.uuid] = n;
+ });
+ this.app.call('scriptChanged', this);
+ }
+ });
+};
+
+ModelWindow.prototype.onEdit = function (data) {
+ if (this.editWindow === undefined) {
+ this.editWindow = new ModelEditWindow({
+ app: this.app,
+ parent: this.parent,
+ callback: this.update.bind(this)
+ });
+ this.editWindow.render();
+ }
+ this.editWindow.setData(data);
+ this.editWindow.show();
+};
+
+ModelWindow.prototype.onDelete = function (data) {
+ var server = this.app.options.server;
+
+ UI.confirm('询问', `是否删除模型${data.Name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${server}/api/Mesh/Delete?ID=${data.ID}`, json => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.update();
+ }
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+export default ModelWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/OptionsWindow.js b/ShadowEditor.Core/src/editor/window/OptionsWindow.js
new file mode 100644
index 00000000..de328590
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/OptionsWindow.js
@@ -0,0 +1,401 @@
+import { UI } from '../../third_party';
+import WebGLRendererSerializer from '../../serialization/core/WebGLRendererSerializer';
+
+/**
+ * 选项窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function OptionsWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+OptionsWindow.prototype = Object.create(UI.Control.prototype);
+OptionsWindow.prototype.constructor = OptionsWindow;
+
+OptionsWindow.prototype.render = function () {
+ var app = this.app;
+ var editor = app.editor;
+ var scene = editor.scene;
+ var renderer = editor.renderer;
+ var shadowMap = renderer.shadowMap;
+
+ this.window = UI.create({
+ xtype: 'window',
+ parent: this.app.container,
+ title: '选项窗口',
+ width: '500px',
+ height: '300px',
+ bodyStyle: {
+ padding: 0
+ },
+ shade: false,
+ children: [{
+ xtype: 'div',
+ cls: 'tabs',
+ children: [{
+ xtype: 'text',
+ id: 'surfaceTab',
+ scope: this.id,
+ text: '外观',
+ cls: 'selected',
+ onClick: () => {
+ this.changeTab('外观');
+ }
+ }, {
+ xtype: 'text',
+ id: 'sceneTab',
+ scope: this.id,
+ text: '场景',
+ onClick: () => {
+ this.changeTab('场景');
+ }
+ }, {
+ xtype: 'text',
+ id: 'rendererTab',
+ scope: this.id,
+ text: '渲染器',
+ onClick: () => {
+ this.changeTab('渲染器');
+ }
+ }]
+ }, { // 外观选项卡
+ xtype: 'div',
+ id: 'surfacePanel',
+ scope: this.id,
+ cls: 'TabPanel',
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '主题'
+ }, {
+ xtype: 'select',
+ id: 'theme',
+ options: {
+ 'assets/css/light.css': '浅色',
+ 'assets/css/dark.css': '深色'
+ },
+ value: app.options.theme,
+ style: {
+ width: '150px'
+ }
+ }]
+ }]
+ }, { // 场景选项卡
+ xtype: 'div',
+ id: 'scenePanel',
+ scope: this.id,
+ cls: 'TabPanel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '背景'
+ }, {
+ xtype: 'color',
+ id: 'backgroundColor',
+ scope: this.id,
+ value: `#${scene.background.getHexString()}`
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '雾'
+ }, {
+ xtype: 'select',
+ id: 'fogType',
+ scope: this.id,
+ options: {
+ 'None': '无',
+ 'Fog': '线性',
+ 'FogExp2': '指数型'
+ },
+ value: scene.fog == null ? 'None' : ((scene.fog instanceof THREE.FogExp2) ? 'FogExp2' : 'Fog'),
+ onChange: this.onChangeFogType.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ id: 'fogColorRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾颜色'
+ }, {
+ xtype: 'color',
+ id: 'fogColor',
+ scope: this.id,
+ value: `#${scene.fog == null ? 'aaaaaa' : scene.fog.color.getHexString()}`
+ }],
+ style: {
+ display: scene.fog == null ? 'none' : ''
+ }
+ }, {
+ xtype: 'row',
+ id: 'fogNearRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾近点'
+ }, {
+ xtype: 'number',
+ id: 'fogNear',
+ scope: this.id,
+ value: (scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.near : 0.1,
+ range: [0, Infinity]
+ }],
+ style: {
+ display: (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none'
+ }
+ }, {
+ xtype: 'row',
+ id: 'fogFarRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾远点'
+ }, {
+ xtype: 'number',
+ id: 'fogFar',
+ scope: this.id,
+ value: (scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.far : 50,
+ range: [0, Infinity]
+ }],
+ style: {
+ display: (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none'
+ }
+ }, {
+ xtype: 'row',
+ id: 'fogDensityRow',
+ scope: this.id,
+ children: [{
+ xtype: 'label',
+ text: '雾浓度'
+ }, {
+ xtype: 'number',
+ id: 'fogDensity',
+ scope: this.id,
+ value: (scene.fog && scene.fog instanceof THREE.FogExp2) ? fog.density : 0.05,
+ range: [0, 0.1],
+ precision: 3
+ }],
+ style: {
+ display: (scene.fog && scene.fog instanceof THREE.FogExp2) ? '' : 'none'
+ }
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '网格'
+ }, {
+ xtype: 'boolean',
+ id: 'showGrid',
+ scope: this.id,
+ value: this.app.editor.grid.visible
+ }]
+ }]
+ }, { // 渲染器选项卡
+ xtype: 'div',
+ id: 'rendererPanel',
+ scope: this.id,
+ cls: 'TabPanel',
+ style: {
+ display: 'none'
+ },
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '阴影'
+ }, {
+ xtype: 'select',
+ id: 'shadowMapType',
+ options: {
+ [-1]: '禁用',
+ [THREE.BasicShadowMap]: '基本阴影', // 0
+ [THREE.PCFShadowMap]: 'PCF阴影', // 1
+ [THREE.PCFSoftShadowMap]: 'PCF软阴影' // 2
+ },
+ value: shadowMap.enabled === false ? -1 : shadowMap.type
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: 'γ输入'
+ }, {
+ xtype: 'boolean',
+ id: 'gammaInput',
+ value: renderer.gammaInput
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: 'γ输出'
+ }, {
+ xtype: 'boolean',
+ id: 'gammaOutput',
+ value: renderer.gammaOutput
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: 'γ因子'
+ }, {
+ xtype: 'number',
+ id: 'gammaFactor',
+ value: renderer.gammaFactor
+ }]
+ }]
+ }],
+ buttons: [{
+ xtype: 'button',
+ text: '保存',
+ onClick: () => {
+ this.save();
+ }
+ }, {
+ xtype: 'button',
+ text: '取消',
+ onClick: () => {
+ this.hide();
+ }
+ }]
+ });
+ this.window.render();
+};
+
+OptionsWindow.prototype.show = function () {
+ this.window.show();
+};
+
+OptionsWindow.prototype.hide = function () {
+ this.window.hide();
+};
+
+OptionsWindow.prototype.changeTab = function (name) {
+ if (name === '外观') {
+ UI.get('surfaceTab', this.id).dom.classList.add('selected');
+ UI.get('sceneTab', this.id).dom.classList.remove('selected');
+ UI.get('rendererTab', this.id).dom.classList.remove('selected');
+ UI.get('surfacePanel', this.id).dom.style.display = '';
+ UI.get('scenePanel', this.id).dom.style.display = 'none';
+ UI.get('rendererPanel', this.id).dom.style.display = 'none';
+ } else if (name === '场景') {
+ UI.get('surfaceTab', this.id).dom.classList.remove('selected');
+ UI.get('sceneTab', this.id).dom.classList.add('selected');
+ UI.get('rendererTab', this.id).dom.classList.remove('selected');
+ UI.get('surfacePanel', this.id).dom.style.display = 'none';
+ UI.get('scenePanel', this.id).dom.style.display = '';
+ UI.get('rendererPanel', this.id).dom.style.display = 'none';
+ } else if (name === '渲染器') {
+ UI.get('surfaceTab', this.id).dom.classList.remove('selected');
+ UI.get('sceneTab', this.id).dom.classList.remove('selected');
+ UI.get('rendererTab', this.id).dom.classList.add('selected');
+ UI.get('surfacePanel', this.id).dom.style.display = 'none';
+ UI.get('scenePanel', this.id).dom.style.display = 'none';
+ UI.get('rendererPanel', this.id).dom.style.display = '';
+ }
+};
+
+OptionsWindow.prototype.onChangeFogType = function () {
+ var fogType = UI.get('fogType', this.id).getValue();
+ var fogColorRow = UI.get('fogColorRow', this.id).dom;
+ var fogNearRow = UI.get('fogNearRow', this.id).dom;
+ var fogFarRow = UI.get('fogFarRow', this.id).dom;
+ var fogDensityRow = UI.get('fogDensityRow', this.id).dom;
+
+ switch (fogType) {
+ case 'None':
+ fogColorRow.style.display = 'none';
+ fogNearRow.style.display = 'none';
+ fogFarRow.style.display = 'none';
+ fogDensityRow.style.display = 'none';
+ break;
+ case 'Fog':
+ fogColorRow.style.display = '';
+ fogNearRow.style.display = '';
+ fogFarRow.style.display = '';
+ fogDensityRow.style.display = 'none';
+ break;
+ case 'FogExp2':
+ fogColorRow.style.display = '';
+ fogNearRow.style.display = 'none';
+ fogFarRow.style.display = 'none';
+ fogDensityRow.style.display = '';
+ break;
+ }
+};
+
+OptionsWindow.prototype.save = function () {
+ // 主题
+ var theme = UI.get('theme').getValue();
+ this.app.options.theme = theme;
+ document.getElementById('theme').href = theme;
+
+ // 场景
+ var scene = this.app.editor.scene;
+
+ var backgroundColor = UI.get('backgroundColor', this.id).getHexValue();
+ scene.background = new THREE.Color(backgroundColor);
+
+ var fogType = UI.get('fogType', this.id).getValue();
+ var fogColor = UI.get('fogColor', this.id).getHexValue();
+ var fogNear = UI.get('fogNear', this.id).getValue();
+ var fogFar = UI.get('fogFar', this.id).getValue();
+ var fogDensity = UI.get('fogDensity', this.id).getValue();
+
+ switch (fogType) {
+ case 'None':
+ scene.fog = null;
+ break;
+ case 'Fog':
+ scene.fog = new THREE.Fog(fogColor, fogNear, fogFar);
+ break;
+ case 'FogExp2':
+ scene.fog = new THREE.FogExp2(fogColor, fogDensity);
+ break;
+ }
+
+ var showGrid = UI.get('showGrid', this.id).getValue();
+ this.app.editor.grid.visible = showGrid;
+
+ // 渲染器
+ var shadowMapType = parseInt(UI.get('shadowMapType').getValue());
+ var gammaInput = UI.get('gammaInput').getValue();
+ var gammaOutput = UI.get('gammaOutput').getValue();
+ var gammaFactor = UI.get('gammaFactor').getValue();
+
+ var renderer = this.app.editor.renderer;
+ var json = (new WebGLRendererSerializer(this.app)).toJSON(renderer);
+ var newRenderer = (new WebGLRendererSerializer(this.app)).fromJSON(json);
+
+ if (shadowMapType === -1) {
+ newRenderer.shadowMap.enabled = false;
+ } else {
+ newRenderer.shadowMap.enabled = true;
+ newRenderer.shadowMap.type = shadowMapType;
+ }
+ newRenderer.gammaInput = gammaInput;
+ newRenderer.gammaOutput = gammaOutput;
+ newRenderer.gammaFactor = gammaFactor;
+
+ this.app.viewport.container.dom.removeChild(renderer.domElement);
+ this.app.viewport.container.dom.appendChild(newRenderer.domElement);
+ this.app.editor.renderer = newRenderer;
+ this.app.editor.renderer.setSize(this.app.viewport.container.dom.offsetWidth, this.app.viewport.container.dom.offsetHeight);
+ this.app.call('render', this);
+
+ // 隐藏窗口
+ this.hide();
+ UI.msg('保存成功。');
+};
+
+export default OptionsWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/SceneEditWindow.js b/ShadowEditor.Core/src/editor/window/SceneEditWindow.js
new file mode 100644
index 00000000..949f6030
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/SceneEditWindow.js
@@ -0,0 +1,125 @@
+import { UI } from '../../third_party';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * 场景编辑窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SceneEditWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.callback = options.callback || null;
+}
+
+SceneEditWindow.prototype = Object.create(UI.Control.prototype);
+SceneEditWindow.prototype.constructor = SceneEditWindow;
+
+SceneEditWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: '编辑场景',
+ width: '320px',
+ height: '280px',
+ shade: true,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '缩略图'
+ }, {
+ xtype: 'imageuploader',
+ id: 'image',
+ scope: this.id,
+ server: this.app.options.server
+ }]
+ }, {
+ xtype: 'row',
+ style: {
+ justifyContent: 'center',
+ marginTop: '8px'
+ },
+ children: [{
+ xtype: 'button',
+ text: '确定',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.save.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ });
+ container.render();
+};
+
+SceneEditWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+};
+
+SceneEditWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+SceneEditWindow.prototype.setData = function (data) {
+ this.data = data;
+ this.updateUI();
+};
+
+SceneEditWindow.prototype.updateUI = function () {
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+ name.setValue(this.data.Name);
+ image.setValue(this.data.Thumbnail);
+};
+
+SceneEditWindow.prototype.save = function () {
+ var server = this.app.options.server;
+
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ var image = UI.get('image', this.id);
+
+ Ajax.post(`${server}/api/Scene/Edit`, {
+ ID: this.data.ID,
+ Name: name.getValue(),
+ Image: image.getValue()
+ }, json => {
+ var obj = JSON.parse(json);
+ UI.msg(obj.Msg);
+ if (obj.Code === 200) {
+ this.hide();
+ if (typeof (this.callback) === 'function') {
+ this.callback(obj);
+ }
+ }
+ });
+};
+
+export default SceneEditWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/SceneWindow.js b/ShadowEditor.Core/src/editor/window/SceneWindow.js
new file mode 100644
index 00000000..d34ebb8c
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/SceneWindow.js
@@ -0,0 +1,205 @@
+import { UI } from '../../third_party';
+import ImageListWindow from '../../ui/ImageListWindow';
+import Ajax from '../../utils/Ajax';
+import AddObjectCommand from '../../command/AddObjectCommand';
+import UploadUtils from '../../utils/UploadUtils';
+import Converter from '../../serialization/Converter';
+import AnimationGroup from '../../animation/AnimationGroup';
+import Animation from '../../animation/Animation';
+import SceneEditWindow from './SceneEditWindow';
+
+/**
+ * 场景窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function SceneWindow(options) {
+ ImageListWindow.call(this, options);
+ this.app = options.app;
+
+ this.title = '场景列表';
+ this.imageIcon = 'icon-scenes';
+ this.preImageUrl = this.app.options.server;
+}
+
+SceneWindow.prototype = Object.create(ImageListWindow.prototype);
+SceneWindow.prototype.constructor = SceneWindow;
+
+SceneWindow.prototype.beforeUpdateList = function () {
+ var server = this.app.options.server;
+
+ return new Promise(resolve => {
+ Ajax.getJson(`${server}/api/Scene/List`, obj => {
+ resolve(obj.Data);
+ });
+ });
+};
+
+SceneWindow.prototype.onClick = function (data) {
+ var app = this.app;
+ var editor = app.editor;
+ var server = app.options.server;
+ document.title = data.Name;
+
+ Ajax.get(`${server}/api/Scene/Load?ID=${data.ID}`, (json) => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.hide();
+ }
+
+ editor.clear(false);
+
+ (new Converter()).fromJson(obj.Data, {
+ server: this.app.options.server,
+ camera: this.app.editor.camera
+ }).then(obj => {
+ this.onLoadScene(obj);
+
+ editor.sceneID = data.ID;
+ editor.sceneName = data.Name;
+ document.title = data.Name;
+
+ // 添加帮助器
+ editor.scene.traverse(n => {
+ editor.addHelper(n);
+ });
+
+ if (obj.options) {
+ this.app.call('optionsChanged', this, this.app.options);
+ }
+
+ if (obj.scripts) {
+ this.app.call('scriptChanged', this);
+ }
+
+ if (obj.animation) {
+ this.app.editor.animation.setAnimationGroups(obj.animation.map(n => {
+ return new AnimationGroup({
+ id: n.id,
+ uuid: n.uuid,
+ name: n.name,
+ index: n.index,
+ animations: n.animations.map(m => {
+ return new Animation({
+ // 基本信息
+ id: m.id,
+ uuid: m.uuid,
+ name: m.name,
+ target: m.target,
+ type: m.type,
+ beginTime: m.beginTime,
+ endTime: m.endTime,
+
+ // 补间动画
+ beginStatus: m.beginStatus,
+ beginPositionX: m.beginPositionX,
+ beginPositionY: m.beginPositionY,
+ beginPositionZ: m.beginPositionZ,
+ beginRotationX: m.beginRotationX,
+ beginRotationY: m.beginRotationY,
+ beginRotationZ: m.beginRotationZ,
+ beginScaleLock: m.beginScaleLock,
+ beginScaleX: m.beginScaleX,
+ beginScaleY: m.beginScaleY,
+ beginScaleZ: m.beginScaleZ,
+ ease: m.ease,
+ endStatus: m.endStatus,
+ endPositionX: m.endPositionX,
+ endPositionY: m.endPositionY,
+ endPositionZ: m.endPositionZ,
+ endRotationX: m.endRotationX,
+ endRotationY: m.endRotationY,
+ endRotationZ: m.endRotationZ,
+ endScaleLock: m.endScaleLock,
+ endScaleX: m.endScaleX,
+ endScaleY: m.endScaleY,
+ endScaleZ: m.endScaleZ
+ })
+ })
+ })
+ }));
+ this.app.call('animationChanged', this);
+ }
+
+ if (obj.scene) {
+ this.app.call('sceneGraphChanged', this);
+ }
+
+ UI.msg('载入成功!');
+ });
+ });
+};
+
+SceneWindow.prototype.onLoadScene = function (obj) {
+ if (obj.options) {
+ Object.assign(this.app.options, obj.options);
+ }
+
+ if (obj.camera) {
+ this.app.editor.camera.copy(obj.camera);
+
+ this.app.editor.camera.children.forEach(n => {
+ if (n instanceof THREE.AudioListener) {
+ this.app.editor.camera.remove(n);
+ }
+ });
+
+ var audioListener = obj.camera.children.filter(n => n instanceof THREE.AudioListener)[0];
+ if (audioListener) {
+ this.app.editor.audioListener = audioListener;
+ this.app.editor.camera.add(audioListener);
+ }
+ }
+
+ if (obj.renderer) {
+ var viewport = this.app.viewport.container.dom;
+ var oldRenderer = this.app.editor.renderer;
+
+ viewport.removeChild(oldRenderer.domElement);
+ viewport.appendChild(obj.renderer.domElement);
+ this.app.editor.renderer = obj.renderer;
+ this.app.editor.renderer.setSize(viewport.offsetWidth, viewport.offsetHeight);
+ this.app.call('resize', this);
+ }
+
+ if (obj.scripts) {
+ Object.assign(this.app.editor.scripts, obj.scripts);
+ }
+
+ if (obj.scene) {
+ this.app.editor.setScene(obj.scene);
+ }
+
+ this.app.editor.camera.updateProjectionMatrix();
+};
+
+SceneWindow.prototype.onEdit = function (data) {
+ if (this.editWindow === undefined) {
+ this.editWindow = new SceneEditWindow({
+ app: this.app,
+ parent: this.parent,
+ callback: this.update.bind(this)
+ });
+ this.editWindow.render();
+ }
+ this.editWindow.setData(data);
+ this.editWindow.show();
+};
+
+SceneWindow.prototype.onDelete = function (data) {
+ var server = this.app.options.server;
+
+ UI.confirm('询问', `是否删除场景${data.Name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${server}/api/Scene/Delete?ID=${data.ID}`, json => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.update();
+ }
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+export default SceneWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/TextureEditWindow.js b/ShadowEditor.Core/src/editor/window/TextureEditWindow.js
new file mode 100644
index 00000000..73e239c2
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/TextureEditWindow.js
@@ -0,0 +1,110 @@
+import { UI } from '../../third_party';
+import Ajax from '../../utils/Ajax';
+
+/**
+ * 纹理编辑窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TextureEditWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+ this.callback = options.callback || null;
+}
+
+TextureEditWindow.prototype = Object.create(UI.Control.prototype);
+TextureEditWindow.prototype.constructor = TextureEditWindow;
+
+TextureEditWindow.prototype.render = function () {
+ var container = UI.create({
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: '编辑纹理',
+ width: '320px',
+ height: '280px',
+ shade: true,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '名称'
+ }, {
+ xtype: 'input',
+ id: 'name',
+ scope: this.id
+ }]
+ }, {
+ xtype: 'row',
+ style: {
+ justifyContent: 'center',
+ marginTop: '8px'
+ },
+ children: [{
+ xtype: 'button',
+ text: '确定',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.save.bind(this)
+ }, {
+ xtype: 'button',
+ text: '取消',
+ style: {
+ margin: '0 8px'
+ },
+ onClick: this.hide.bind(this)
+ }]
+ }]
+ });
+ container.render();
+};
+
+TextureEditWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+};
+
+TextureEditWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+TextureEditWindow.prototype.setData = function (data) {
+ this.data = data;
+ this.updateUI();
+};
+
+TextureEditWindow.prototype.updateUI = function () {
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+ name.setValue(this.data.Name);
+};
+
+TextureEditWindow.prototype.save = function () {
+ var server = this.app.options.server;
+
+ if (this.data === undefined) {
+ return;
+ }
+
+ var name = UI.get('name', this.id);
+
+ Ajax.post(`${server}/api/Texture/Edit`, {
+ ID: this.data.ID,
+ Name: name.getValue()
+ }, json => {
+ var obj = JSON.parse(json);
+ UI.msg(obj.Msg);
+ if (obj.Code === 200) {
+ this.hide();
+ if (typeof (this.callback) === 'function') {
+ this.callback(obj);
+ }
+ }
+ });
+};
+
+export default TextureEditWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/TextureSettingWindow.js b/ShadowEditor.Core/src/editor/window/TextureSettingWindow.js
new file mode 100644
index 00000000..47c37f53
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/TextureSettingWindow.js
@@ -0,0 +1,461 @@
+import { UI } from '../../third_party';
+
+/**
+ * 纹理设置窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TextureSettingWindow(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+}
+
+TextureSettingWindow.prototype = Object.create(UI.Control.prototype);
+TextureSettingWindow.prototype.constructor = TextureSettingWindow;
+
+TextureSettingWindow.prototype.render = function () {
+ this.window = UI.create({
+ xtype: 'window',
+ title: '纹理设置',
+ width: '300px',
+ height: '450px',
+ bodyStyle: {
+ padding: 0
+ },
+ shade: false,
+ children: [{
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '偏移'
+ }, {
+ xtype: 'number',
+ id: 'offsetX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'offsetY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '旋转中心'
+ }, {
+ xtype: 'number',
+ id: 'centerX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'number',
+ id: 'centerY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '旋转'
+ }, {
+ xtype: 'number',
+ id: 'rotation',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '横向填充'
+ }, {
+ xtype: 'select',
+ id: 'wrapS',
+ scope: this.id,
+ options: {
+ [THREE.ClampToEdgeWrapping]: '拉伸',
+ [THREE.RepeatWrapping]: '重复',
+ [THREE.MirroredRepeatWrapping]: '镜像'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '纵向填充'
+ }, {
+ xtype: 'select',
+ id: 'wrapT',
+ scope: this.id,
+ options: {
+ [THREE.ClampToEdgeWrapping]: '拉伸',
+ [THREE.RepeatWrapping]: '重复',
+ [THREE.MirroredRepeatWrapping]: '镜像'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '填充次数'
+ }, {
+ xtype: 'int',
+ id: 'repeatX',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }, {
+ xtype: 'int',
+ id: 'repeatY',
+ scope: this.id,
+ style: {
+ width: '40px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '反转Y'
+ }, {
+ xtype: 'checkbox',
+ id: 'flipY',
+ scope: this.id,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '放大过滤'
+ }, {
+ xtype: 'select',
+ id: 'magFilter',
+ scope: this.id,
+ options: {
+ [THREE.LinearFilter]: 'LinearFilter',
+ [THREE.NearestFilter]: 'NearestFilter'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '缩小过滤'
+ }, {
+ xtype: 'select',
+ id: 'minFilter',
+ scope: this.id,
+ options: {
+ [THREE.LinearMipMapLinearFilter]: 'LinearMipMapLinearFilter',
+ [THREE.NearestFilter]: 'NearestFilter',
+ [THREE.NearestMipMapNearestFilter]: 'NearestMipMapNearestFilter',
+ [THREE.NearestMipMapLinearFilter]: 'NearestMipMapLinearFilter',
+ [THREE.LinearFilter]: 'LinearFilter',
+ [THREE.LinearMipMapNearestFilter]: 'LinearMipMapNearestFilter'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '类型'
+ }, {
+ xtype: 'select',
+ id: 'type',
+ scope: this.id,
+ options: {
+ [THREE.UnsignedByteType]: 'UnsignedByteType',
+ [THREE.ByteType]: 'ByteType',
+ [THREE.ShortType]: 'ShortType',
+ [THREE.UnsignedShortType]: 'UnsignedShortType',
+ [THREE.IntType]: 'IntType',
+ [THREE.UnsignedIntType]: 'UnsignedIntType',
+ [THREE.FloatType]: 'FloatType',
+ [THREE.HalfFloatType]: 'HalfFloatType',
+ [THREE.UnsignedShort4444Type]: 'UnsignedShort4444Type',
+ [THREE.UnsignedShort5551Type]: 'UnsignedShort5551Type',
+ [THREE.UnsignedShort565Type]: 'UnsignedShort565Type',
+ [THREE.UnsignedInt248Type]: 'UnsignedInt248Type'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '编码'
+ }, {
+ xtype: 'select',
+ id: 'encoding',
+ scope: this.id,
+ options: {
+ [THREE.LinearEncoding]: 'LinearEncoding',
+ [THREE.sRGBEncoding]: 'sRGBEncoding',
+ [THREE.GammaEncoding]: 'GammaEncoding',
+ [THREE.RGBEEncoding]: 'RGBEEncoding',
+ [THREE.LogLuvEncoding]: 'LogLuvEncoding',
+ [THREE.RGBM7Encoding]: 'RGBM7Encoding',
+ [THREE.RGBM16Encoding]: 'RGBM16Encoding',
+ [THREE.RGBDEncoding]: 'RGBDEncoding',
+ [THREE.BasicDepthPacking]: 'BasicDepthPacking',
+ [THREE.RGBADepthPacking]: 'RGBADepthPacking'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '格式'
+ }, {
+ xtype: 'select',
+ id: 'format',
+ scope: this.id,
+ options: {
+ [THREE.RGBAFormat]: 'RGBAFormat',
+ [THREE.AlphaFormat]: 'AlphaFormat',
+ [THREE.RGBFormat]: 'RGBFormat',
+ [THREE.LuminanceFormat]: 'LuminanceFormat',
+ [THREE.LuminanceAlphaFormat]: 'LuminanceAlphaFormat',
+ [THREE.RGBEFormat]: 'RGBEFormat',
+ [THREE.DepthFormat]: 'DepthFormat',
+ [THREE.DepthStencilFormat]: 'DepthStencilFormat'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '开启层级'
+ }, {
+ xtype: 'checkbox',
+ id: 'generateMipmaps',
+ scope: this.id,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '映射'
+ }, {
+ xtype: 'select',
+ id: 'mapping',
+ scope: this.id,
+ options: {
+ [THREE.UVMapping]: 'UVMapping',
+ [THREE.CubeReflectionMapping]: 'CubeReflectionMapping',
+ [THREE.CubeRefractionMapping]: 'CubeRefractionMapping',
+ [THREE.EquirectangularReflectionMapping]: 'EquirectangularReflectionMapping',
+ [THREE.EquirectangularRefractionMapping]: 'EquirectangularRefractionMapping',
+ [THREE.SphericalReflectionMapping]: 'SphericalReflectionMapping',
+ [THREE.CubeUVReflectionMapping]: 'CubeUVReflectionMapping',
+ [THREE.CubeUVRefractionMapping]: 'CubeUVRefractionMapping'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '左乘透明'
+ }, {
+ xtype: 'checkbox',
+ id: 'premultiplyAlpha',
+ scope: this.id,
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '解压对齐'
+ }, {
+ xtype: 'select',
+ id: 'unpackAlignment',
+ scope: this.id,
+ options: {
+ [4]: '4',
+ [1]: '1',
+ [2]: '2',
+ [8]: '8'
+ },
+ style: {
+ width: '160px'
+ },
+ onChange: this.onChange.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'label',
+ text: '各向异性'
+ }, {
+ xtype: 'int',
+ id: 'anisotropy',
+ scope: this.id,
+ style: {
+ width: '80px'
+ },
+ range: [1, 16],
+ onChange: this.onChange.bind(this)
+ }]
+ }]
+ });
+ this.window.render();
+};
+
+TextureSettingWindow.prototype.show = function () {
+ this.window.show();
+};
+
+TextureSettingWindow.prototype.hide = function () {
+ this.window.hide();
+};
+
+TextureSettingWindow.prototype.setData = function (texture) {
+ var anisotropy = UI.get('anisotropy', this.id);
+ var centerX = UI.get('centerX', this.id);
+ var centerY = UI.get('centerY', this.id);
+ var offsetX = UI.get('offsetX', this.id);
+ var offsetY = UI.get('offsetY', this.id);
+ var repeatX = UI.get('repeatX', this.id);
+ var repeatY = UI.get('repeatY', this.id);
+ var rotation = UI.get('rotation', this.id);
+ var type = UI.get('type', this.id);
+ var encoding = UI.get('encoding', this.id);
+ var flipY = UI.get('flipY', this.id);
+ var format = UI.get('format', this.id);
+ var generateMipmaps = UI.get('generateMipmaps', this.id);
+ var magFilter = UI.get('magFilter', this.id);
+ var minFilter = UI.get('minFilter', this.id);
+ var mapping = UI.get('mapping', this.id);
+ var premultiplyAlpha = UI.get('premultiplyAlpha', this.id);
+ var unpackAlignment = UI.get('unpackAlignment', this.id);
+ var wrapS = UI.get('wrapS', this.id);
+ var wrapT = UI.get('wrapT', this.id);
+
+ this.texture = texture;
+
+ anisotropy.setValue(texture.anisotropy);
+ centerX.setValue(texture.center.x);
+ centerY.setValue(texture.center.y);
+ offsetX.setValue(texture.offset.x);
+ offsetY.setValue(texture.offset.y);
+ repeatX.setValue(texture.repeat.x);
+ repeatY.setValue(texture.repeat.y);
+ rotation.setValue(texture.rotation);
+ type.setValue(texture.type);
+ encoding.setValue(texture.encoding);
+ flipY.setValue(texture.flipY);
+ format.setValue(texture.format);
+ generateMipmaps.setValue(texture.generateMipmaps);
+ magFilter.setValue(texture.magFilter);
+ minFilter.setValue(texture.minFilter);
+ mapping.setValue(texture.mapping);
+ premultiplyAlpha.setValue(texture.premultiplyAlpha);
+ unpackAlignment.setValue(texture.unpackAlignment);
+ wrapS.setValue(texture.wrapS);
+ wrapT.setValue(texture.wrapT);
+};
+
+TextureSettingWindow.prototype.onChange = function () {
+ var anisotropy = UI.get('anisotropy', this.id);
+ var centerX = UI.get('centerX', this.id);
+ var centerY = UI.get('centerY', this.id);
+ var offsetX = UI.get('offsetX', this.id);
+ var offsetY = UI.get('offsetY', this.id);
+ var repeatX = UI.get('repeatX', this.id);
+ var repeatY = UI.get('repeatY', this.id);
+ var rotation = UI.get('rotation', this.id);
+ var type = UI.get('type', this.id);
+ var encoding = UI.get('encoding', this.id);
+ var flipY = UI.get('flipY', this.id);
+ var format = UI.get('format', this.id);
+ var generateMipmaps = UI.get('generateMipmaps', this.id);
+ var magFilter = UI.get('magFilter', this.id);
+ var minFilter = UI.get('minFilter', this.id);
+ var mapping = UI.get('mapping', this.id);
+ var premultiplyAlpha = UI.get('premultiplyAlpha', this.id);
+ var unpackAlignment = UI.get('unpackAlignment', this.id);
+ var wrapS = UI.get('wrapS', this.id);
+ var wrapT = UI.get('wrapT', this.id);
+
+ var texture = this.texture;
+
+ texture.anisotropy = anisotropy.getValue();
+ texture.center.x = centerX.getValue();
+ texture.center.y = centerY.getValue();
+ texture.offset.x = offsetX.getValue();
+ texture.offset.y = offsetY.getValue();
+ texture.repeat.x = repeatX.getValue();
+ texture.repeat.y = repeatY.getValue();
+ texture.rotation = rotation.getValue();
+ texture.type = parseInt(type.getValue());
+ texture.encoding = parseInt(encoding.getValue());
+ texture.flipY = flipY.getValue();
+ texture.format = parseInt(format.getValue());
+ texture.generateMipmaps = generateMipmaps.getValue();
+ texture.magFilter = parseInt(magFilter.getValue());
+ texture.minFilter = parseInt(minFilter.getValue());
+ texture.mapping = parseInt(mapping.getValue());
+ texture.premultiplyAlpha = premultiplyAlpha.getValue();
+ texture.unpackAlignment = parseInt(unpackAlignment.getValue());
+ texture.wrapS = parseInt(wrapS.getValue());
+ texture.wrapT = parseInt(wrapT.getValue());
+
+ texture.needsUpdate = true;
+};
+
+export default TextureSettingWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/editor/window/TextureWindow.js b/ShadowEditor.Core/src/editor/window/TextureWindow.js
new file mode 100644
index 00000000..7c435b32
--- /dev/null
+++ b/ShadowEditor.Core/src/editor/window/TextureWindow.js
@@ -0,0 +1,82 @@
+import { UI } from '../../third_party';
+import ImageListWindow from '../../ui/ImageListWindow';
+import Ajax from '../../utils/Ajax';
+import UploadUtils from '../../utils/UploadUtils';
+import TextureEditWindow from './TextureEditWindow';
+
+/**
+ * 纹理窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function TextureWindow(options) {
+ ImageListWindow.call(this, options);
+ this.app = options.app;
+
+ this.title = '纹理列表';
+
+ this.imageIcon = 'icon-texture';
+ // this.cornerTextField = 'Type';
+ this.uploadUrl = `${this.app.options.server}/api/Texture/Add`;
+ this.preImageUrl = this.app.options.server;
+ this.showUploadButton = true;
+
+ this.onSelect = options.onSelect || null;
+}
+
+TextureWindow.prototype = Object.create(ImageListWindow.prototype);
+TextureWindow.prototype.constructor = TextureWindow;
+
+TextureWindow.prototype.beforeUpdateList = function () {
+ var server = this.app.options.server;
+
+ return new Promise(resolve => {
+ Ajax.getJson(`${server}/api/Texture/List`, obj => {
+ resolve(obj.Data);
+ });
+ });
+};
+
+TextureWindow.prototype.onUpload = function (obj) {
+ this.update();
+ UI.msg(obj.Msg);
+};
+
+TextureWindow.prototype.onClick = function (data) {
+ if (typeof (this.onSelect) === 'function') {
+ this.onSelect(data);
+ } else {
+ UI.msg('请在材质控件中修改纹理。');
+ }
+};
+
+TextureWindow.prototype.onEdit = function (data) {
+ if (this.editWindow === undefined) {
+ this.editWindow = new TextureEditWindow({
+ app: this.app,
+ parent: this.parent,
+ callback: this.update.bind(this)
+ });
+ this.editWindow.render();
+ }
+ this.editWindow.setData(data);
+ this.editWindow.show();
+};
+
+TextureWindow.prototype.onDelete = function (data) {
+ var server = this.app.options.server;
+
+ UI.confirm('询问', `是否删除纹理${data.Name}?`, (event, btn) => {
+ if (btn === 'ok') {
+ Ajax.post(`${server}/api/Texture/Delete?ID=${data.ID}`, json => {
+ var obj = JSON.parse(json);
+ if (obj.Code === 200) {
+ this.update();
+ }
+ UI.msg(obj.Msg);
+ });
+ }
+ });
+};
+
+export default TextureWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/effect/BaseEffect.js b/ShadowEditor.Core/src/effect/BaseEffect.js
new file mode 100644
index 00000000..6cb24824
--- /dev/null
+++ b/ShadowEditor.Core/src/effect/BaseEffect.js
@@ -0,0 +1,17 @@
+var ID = -1;
+
+/**
+ * 特效基类
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function BaseEffect(app) {
+ this.app = app;
+ this.id = `BaseEffect${ID--}`;
+};
+
+BaseEffect.prototype.render = function (obj) {
+
+};
+
+export default BaseEffect;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/effect/SelectEffect.js b/ShadowEditor.Core/src/effect/SelectEffect.js
new file mode 100644
index 00000000..0ebc86c1
--- /dev/null
+++ b/ShadowEditor.Core/src/effect/SelectEffect.js
@@ -0,0 +1,50 @@
+import BaseEffect from './BaseEffect';
+import SelectVertexShader from './shader/select_vertex.glsl';
+import SelectFragmentShader from './shader/select_fragment.glsl';
+
+/**
+ * 选中特效
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function SelectEffect(app) {
+ BaseEffect.call(this, app);
+ this.scene = new THREE.Scene();
+};
+
+SelectEffect.prototype = Object.create(BaseEffect.prototype);
+SelectEffect.prototype.constructor = SelectEffect;
+
+SelectEffect.prototype.render = function (obj) {
+ if (obj instanceof THREE.SkinnedMesh) {
+ return;
+ }
+
+ var renderer = this.app.editor.renderer;
+ var scene = this.scene;
+ var camera = this.app.editor.camera;
+
+ var geometry = obj.geometry;
+
+ var material = new THREE.RawShaderMaterial({
+ uniforms: {
+ time: {
+ value: 1.0
+ }
+ },
+ vertexShader: SelectVertexShader,
+ fragmentShader: SelectFragmentShader
+ });
+
+ var mesh = new THREE.Mesh(geometry, material);
+ mesh.position.copy(obj.position);
+ mesh.rotation.copy(obj.rotation);
+ mesh.scale.copy(obj.scale);
+
+ scene.children.length = 0;
+ scene.add(mesh);
+
+ renderer.render(scene, camera);
+};
+
+export default SelectEffect;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/effect/shader/select_fragment.glsl b/ShadowEditor.Core/src/effect/shader/select_fragment.glsl
new file mode 100644
index 00000000..8e9c4e1a
--- /dev/null
+++ b/ShadowEditor.Core/src/effect/shader/select_fragment.glsl
@@ -0,0 +1,5 @@
+precision mediump float;
+
+void main() {
+ gl_FragColor = vec4(0.925, 0.396, 0.102, 1.0); // 0xec651a
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/effect/shader/select_vertex.glsl b/ShadowEditor.Core/src/effect/shader/select_vertex.glsl
new file mode 100644
index 00000000..98cdef73
--- /dev/null
+++ b/ShadowEditor.Core/src/effect/shader/select_vertex.glsl
@@ -0,0 +1,12 @@
+precision mediump float;
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+attribute vec3 position;
+attribute vec3 normal;
+
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position + normal * 0.01, 1.0);
+ gl_Position.z = gl_Position.z + 0.1;
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/AnimateEvent.js b/ShadowEditor.Core/src/event/AnimateEvent.js
new file mode 100644
index 00000000..e78f4b4b
--- /dev/null
+++ b/ShadowEditor.Core/src/event/AnimateEvent.js
@@ -0,0 +1,41 @@
+import BaseEvent from './BaseEvent';
+
+/**
+ * 动画事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function AnimateEvent(app) {
+ BaseEvent.call(this, app);
+ this.running = false;
+ this.clock = new THREE.Clock();
+}
+
+AnimateEvent.prototype = Object.create(BaseEvent.prototype);
+AnimateEvent.prototype.constructor = AnimateEvent;
+
+AnimateEvent.prototype.start = function () {
+ this.running = true;
+ requestAnimationFrame(this.onAnimate.bind(this));
+};
+
+AnimateEvent.prototype.stop = function () {
+ this.running = false;
+};
+
+AnimateEvent.prototype.onAnimate = function () {
+ this.app.editor.stats.begin();
+
+ var deltaTime = this.clock.getDelta();
+
+ this.app.call('animate', this, this.clock, deltaTime);
+ this.app.call('render', this);
+
+ this.app.editor.stats.end();
+
+ if (this.running) {
+ requestAnimationFrame(this.onAnimate.bind(this));
+ }
+};
+
+export default AnimateEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/BaseEvent.js b/ShadowEditor.Core/src/event/BaseEvent.js
new file mode 100644
index 00000000..eeb26549
--- /dev/null
+++ b/ShadowEditor.Core/src/event/BaseEvent.js
@@ -0,0 +1,26 @@
+var ID = -1;
+
+/**
+ * 事件基类
+ * @author tengge / https://github.com/tengge1
+ */
+function BaseEvent(app) {
+ this.app = app;
+ this.id = `BaseEvent${ID--}`;
+}
+
+/**
+ * 开始执行
+ */
+BaseEvent.prototype.start = function () {
+
+};
+
+/**
+ * 停止执行
+ */
+BaseEvent.prototype.stop = function () {
+
+};
+
+export default BaseEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/EditorControlsEvent.js b/ShadowEditor.Core/src/event/EditorControlsEvent.js
new file mode 100644
index 00000000..b61406d4
--- /dev/null
+++ b/ShadowEditor.Core/src/event/EditorControlsEvent.js
@@ -0,0 +1,49 @@
+import BaseEvent from './BaseEvent';
+
+/**
+ * 编辑器控件事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function EditorControlsEvent(app) {
+ BaseEvent.call(this, app);
+}
+
+EditorControlsEvent.prototype = Object.create(BaseEvent.prototype);
+EditorControlsEvent.prototype.constructor = EditorControlsEvent;
+
+EditorControlsEvent.prototype.start = function () {
+ var controls = this.app.editor.controls;
+
+ controls.addEventListener('change', this.onChange.bind(this));
+
+ this.app.on('editorCleared.' + this.id, this.onEditorCleared.bind(this));
+};
+
+EditorControlsEvent.prototype.stop = function () {
+ var controls = this.app.editor.controls;
+
+ controls.removeEventListener('change', this.onChange);
+
+ this.app.on('editorCleared.' + this.id, null);
+};
+
+EditorControlsEvent.prototype.onChange = function () {
+ var editor = this.app.editor;
+ var transformControls = editor.transformControls;
+ var camera = editor.camera;
+
+ transformControls.update();
+
+ this.app.call('cameraChanged', this, camera);
+};
+
+EditorControlsEvent.prototype.onEditorCleared = function () {
+ var controls = this.app.editor.controls;
+
+ controls.center.set(0, 0, 0);
+
+ this.app.call('render');
+};
+
+export default EditorControlsEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/EventDispatcher.js b/ShadowEditor.Core/src/event/EventDispatcher.js
new file mode 100644
index 00000000..c557694b
--- /dev/null
+++ b/ShadowEditor.Core/src/event/EventDispatcher.js
@@ -0,0 +1,135 @@
+import { dispatch } from '../third_party';
+import EventList from './EventList';
+import BaseEvent from './BaseEvent';
+
+// 核心
+import AnimateEvent from './AnimateEvent';
+import KeyDownEvent from './KeyDownEvent';
+import RenderEvent from './RenderEvent';
+import ResizeEvent from './ResizeEvent';
+
+// 菜单栏
+import AddPhysicsPlaneEvent from './menu/physics/AddPhysicsPlaneEvent';
+import AddPhysicsWallEvent from './menu/physics/AddPhysicsWallEvent';
+import AddPhysicsClothEvent from './menu/physics/AddPhysicsClothEvent';
+
+import TransformControlsEvent from './TransformControlsEvent';
+import ObjectEvent from './ObjectEvent';
+import PickEvent from './PickEvent';
+import EditorControlsEvent from './EditorControlsEvent';
+
+/**
+ * 事件执行器
+ * @author tengge / https://github.com/tengge1
+ */
+function EventDispatcher(app) {
+ this.app = app;
+ this.dispatch = dispatch.apply(dispatch, EventList);
+ this.addDomEventListener();
+
+ this.events = [
+ // 核心事件
+ new AnimateEvent(this.app),
+ new KeyDownEvent(this.app),
+ new RenderEvent(this.app),
+ new ResizeEvent(this.app),
+
+ // menubar中的事件
+ new AddPhysicsPlaneEvent(this.app),
+ new AddPhysicsWallEvent(this.app),
+ new AddPhysicsClothEvent(this.app),
+
+ // viewport中的事件
+ new TransformControlsEvent(this.app),
+ new ObjectEvent(this.app),
+ new PickEvent(this.app),
+ new EditorControlsEvent(this.app)
+ ];
+}
+
+EventDispatcher.prototype = Object.create(BaseEvent.prototype);
+EventDispatcher.prototype.constructor = EventDispatcher;
+
+/**
+ * 启动
+ */
+EventDispatcher.prototype.start = function () {
+ this.events.forEach(n => {
+ n.start();
+ });
+};
+
+/**
+ * 停止
+ */
+EventDispatcher.prototype.stop = function () {
+ this.events.forEach(n => {
+ n.stop();
+ });
+};
+
+/**
+ * 执行事件
+ * @param {*} eventName
+ * @param {*} _this
+ * @param {*} others
+ */
+EventDispatcher.prototype.call = function (eventName, _this, ...others) {
+ this.dispatch.call(eventName, _this, ...others);
+};
+
+/**
+ * 监听事件
+ * @param {*} eventName
+ * @param {*} callback
+ */
+EventDispatcher.prototype.on = function (eventName, callback) {
+ this.dispatch.on(eventName, callback);
+};
+
+/**
+ * 监听dom事件
+ */
+EventDispatcher.prototype.addDomEventListener = function () {
+ var container = this.app.container;
+ container.addEventListener('click', event => {
+ this.dispatch.call('click', this, event);
+ });
+ container.addEventListener('contextmenu', event => {
+ this.dispatch.call('contextmenu', this, event);
+ event.preventDefault();
+ return false;
+ });
+ container.addEventListener('dblclick', event => {
+ this.dispatch.call('dblclick', this, event);
+ });
+ document.addEventListener('keydown', event => {
+ this.dispatch.call('keydown', this, event);
+ });
+ document.addEventListener('keyup', event => {
+ this.dispatch.call('keyup', this, event);
+ });
+ container.addEventListener('mousedown', event => {
+ this.dispatch.call('mousedown', this, event);
+ });
+ container.addEventListener('mousemove', event => {
+ this.dispatch.call('mousemove', this, event);
+ });
+ container.addEventListener('mouseup', event => {
+ this.dispatch.call('mouseup', this, event);
+ });
+ container.addEventListener('mousewheel', event => {
+ this.dispatch.call('mousewheel', this, event);
+ });
+ window.addEventListener('resize', event => {
+ this.dispatch.call('resize', this, event);
+ }, false);
+ document.addEventListener('dragover', event => {
+ this.dispatch.call('dragover', this, event);
+ }, false);
+ document.addEventListener('drop', event => {
+ this.dispatch.call('drop', this, event);
+ }, false);
+};
+
+export default EventDispatcher;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/EventList.js b/ShadowEditor.Core/src/event/EventList.js
new file mode 100644
index 00000000..7c912603
--- /dev/null
+++ b/ShadowEditor.Core/src/event/EventList.js
@@ -0,0 +1,93 @@
+/**
+ * 自定义事件列表
+ * @author tengge / https://github.com/tengge1
+ */
+var EventList = [
+ // dom事件
+ 'click', // 点击
+ 'contextmenu', // 右键
+ 'dblclick', // 双击
+ 'keydown', // 按下键盘按键
+ 'keyup', // 抬起键盘按键
+ 'mousedown', // 按下鼠标按键
+ 'mousemove', // 鼠标移动
+ 'mouseup', // 抬起鼠标按键
+ 'mousewheel', // 鼠标滚轮
+ 'resize', // 窗口大小改变
+ 'dragover', // 拖动到某元素上
+ 'drop', // 放置到某元素上
+
+ // app事件
+ 'appStart', // 应用程序开始前调用
+ 'appStarted', // 应用程序开始后调用
+ 'appStop', // 程序开始结束前调用
+ 'appStoped', // 程序结束后调用
+
+ // 配置
+ 'optionsChanged', // 配置改变事件
+
+ // 菜单栏事件
+ 'mAddMiku',
+
+ 'mAddPhysicsPlane', // 添加平板
+ 'mAddPhysicsWall', // 添加墙
+ 'mAddPhysicsCloth', // 添加布料
+ 'mThrowBall', // 探测小球
+
+ // 工具栏事件
+
+ 'changeMode', // 改变模式(select, translate, rotate, scale, delete)
+
+ // editor事件
+ 'select', // 选中事件
+ 'clear', // 清空场景
+ 'load', // 加载场景
+ 'log', // 日志事件
+
+ // signal事件
+ 'editScript', // 编辑脚本事件
+
+ 'editorCleared', // 编辑器已经清空事件
+
+ 'snapChanged', // 对齐单元格事件
+ 'spaceChanged', // 空间坐标系改变事件
+
+ 'sceneGraphChanged', // 场景内容改变事件
+
+ 'cameraChanged', // 相机改变事件
+
+ 'geometryChanged', // 几何体改变事件
+
+ 'objectSelected', // 物体选中改变
+ 'objectFocused', // 物体交点改变事件
+
+ 'objectAdded', // 添加物体事件
+ 'objectChanged', // 物体改变事件
+ 'objectRemoved', // 物体移除事件
+
+ 'materialChanged', // 材质改变事件
+
+ 'scriptAdded', // 添加脚本事件
+ 'scriptChanged', // 脚本改变事件
+ 'scriptRemoved', // 脚本移除事件
+
+ 'historyChanged', // 历史改变事件
+ 'refreshScriptEditor', // 刷新脚本编辑器事件
+
+ // 场景编辑区
+ 'transformControlsChange', // 变形控件改变
+ 'transformControlsMouseDown', // 变形控件按下鼠标键
+ 'transformControlsMouseUp', // 变形控件抬起鼠标键
+ 'render', // 渲染一次场景
+ 'animate', // 进行动画
+
+ // 侧边栏
+ 'tabSelected', // 选项卡选中事件
+ 'animationSelected', // 动画选中事件
+ 'animationChanged', // 动画发生改变事件
+ 'resetAnimation', // 重制动画时间轴
+ 'startAnimation', // 开始播放动画
+ 'animationTime', // 时间轴发送当前动画时间
+];
+
+export default EventList;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/KeyDownEvent.js b/ShadowEditor.Core/src/event/KeyDownEvent.js
new file mode 100644
index 00000000..b7bc97f8
--- /dev/null
+++ b/ShadowEditor.Core/src/event/KeyDownEvent.js
@@ -0,0 +1,68 @@
+import BaseEvent from './BaseEvent';
+import RemoveObjectCommand from '../command/RemoveObjectCommand';
+import { UI } from '../third_party';
+
+/**
+ * 键盘按键事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function KeyDownEvent(app) {
+ BaseEvent.call(this, app);
+}
+
+KeyDownEvent.prototype = Object.create(BaseEvent.prototype);
+KeyDownEvent.prototype.constructor = KeyDownEvent;
+
+KeyDownEvent.prototype.start = function () {
+ this.app.on(`keydown.${this.id}`, this.onKeyDown.bind(this));
+};
+
+KeyDownEvent.prototype.stop = function () {
+ this.app.on(`keydown.${this.id}`, null);
+};
+
+KeyDownEvent.prototype.onKeyDown = function (event) {
+ var editor = this.app.editor;
+
+ switch (event.keyCode) {
+ case 8: // 回退键
+ event.preventDefault(); // 阻止浏览器返回
+ break;
+
+ case 46: // 删除键
+ var object = editor.selected;
+ if (object == null) {
+ return;
+ }
+ UI.confirm('询问', '删除 ' + object.name + '?', function (event, btn) {
+ if (btn === 'ok') {
+ var parent = object.parent;
+ if (parent !== null) editor.execute(new RemoveObjectCommand(object));
+ }
+ });
+ break;
+
+ case 90: // 注册Ctrl-Z撤销, Ctrl-Shift-Z重做
+ if (event.ctrlKey && event.shiftKey) {
+ editor.redo();
+ } else if (event.ctrlKey) {
+ editor.undo();
+ }
+ break;
+
+ case 87: // 注册 W 移动模式
+ this.app.call('changeMode', this, 'translate');
+ break;
+
+ case 69: // 注册 E 旋转模式
+ this.app.call('changeMode', this, 'rotate');
+ break;
+
+ case 82: // 注册 R 缩放模式
+ this.app.call('changeMode', this, 'scale');
+ break;
+ }
+};
+
+export default KeyDownEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/ObjectEvent.js b/ShadowEditor.Core/src/event/ObjectEvent.js
new file mode 100644
index 00000000..c4426bc9
--- /dev/null
+++ b/ShadowEditor.Core/src/event/ObjectEvent.js
@@ -0,0 +1,81 @@
+import BaseEvent from './BaseEvent';
+
+/**
+ * 物体事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function ObjectEvent(app) {
+ BaseEvent.call(this, app);
+ this.box = new THREE.Box3();
+}
+
+ObjectEvent.prototype = Object.create(BaseEvent.prototype);
+ObjectEvent.prototype.constructor = ObjectEvent;
+
+ObjectEvent.prototype.start = function () {
+ this.app.on('objectAdded.' + this.id, this.onObjectAdded.bind(this));
+ this.app.on('objectChanged.' + this.id, this.onObjectChanged.bind(this));
+ this.app.on('objectRemoved.' + this.id, this.onObjectRemoved.bind(this));
+ this.app.on('objectSelected.' + this.id, this.onObjectSelected.bind(this));
+ this.app.on('objectFocused.' + this.id, this.onObjectFocused.bind(this));
+};
+
+ObjectEvent.prototype.stop = function () {
+ this.app.on('objectAdded.' + this.id, null);
+ this.app.on('objectChanged.' + this.id, null);
+ this.app.on('objectRemoved.' + this.id, null);
+ this.app.on('objectSelected.' + this.id, null);
+ this.app.on('objectFocused.' + this.id, null);
+};
+
+ObjectEvent.prototype.onObjectAdded = function (object) {
+ var objects = this.app.editor.objects;
+
+ object.traverse(function (child) {
+ objects.push(child);
+ });
+};
+
+ObjectEvent.prototype.onObjectChanged = function (object) {
+ var editor = this.app.editor;
+ var transformControls = editor.transformControls;
+
+ if (editor.selected === object) {
+ transformControls.update();
+ }
+
+ if (object instanceof THREE.PerspectiveCamera) {
+ object.updateProjectionMatrix();
+ }
+
+ if (editor.helpers[object.id] !== undefined && !(editor.helpers[object.id] instanceof THREE.SkeletonHelper)) {
+ editor.helpers[object.id].update();
+ }
+
+ this.app.call('render');
+};
+
+ObjectEvent.prototype.onObjectRemoved = function (object) {
+ var objects = this.app.editor.objects;
+
+ object.traverse(function (child) {
+ objects.splice(objects.indexOf(child), 1);
+ });
+};
+
+ObjectEvent.prototype.onObjectSelected = function (object) {
+ var editor = this.app.editor;
+ var scene = editor.scene;
+ var box = this.box;
+
+ this.app.call('render');
+};
+
+ObjectEvent.prototype.onObjectFocused = function (object) {
+ var controls = this.app.editor.controls;
+
+ controls.focus(object);
+};
+
+export default ObjectEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/PickEvent.js b/ShadowEditor.Core/src/event/PickEvent.js
new file mode 100644
index 00000000..86c98d03
--- /dev/null
+++ b/ShadowEditor.Core/src/event/PickEvent.js
@@ -0,0 +1,109 @@
+import BaseEvent from './BaseEvent';
+
+/**
+ * 选取事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function PickEvent(app) {
+ BaseEvent.call(this, app);
+
+ this.raycaster = new THREE.Raycaster();
+ this.mouse = new THREE.Vector2();
+
+ this.onDownPosition = new THREE.Vector2();
+ this.onUpPosition = new THREE.Vector2();
+ this.onDoubleClickPosition = new THREE.Vector2();
+}
+
+PickEvent.prototype = Object.create(BaseEvent.prototype);
+PickEvent.prototype.constructor = PickEvent;
+
+PickEvent.prototype.start = function () {
+ var container = this.app.viewport.container;
+
+ container.dom.addEventListener('mousedown', this.onMouseDown.bind(this), false);
+ container.dom.addEventListener('dblclick', this.onDoubleClick.bind(this), false);
+};
+
+PickEvent.prototype.stop = function () {
+ var container = this.app.viewport.container;
+
+ container.dom.removeEventListener('mousedown', this.onMouseDown, false);
+ container.dom.removeEventListener('dblclick', this.onDoubleClick, false);
+};
+
+PickEvent.prototype.onMouseDown = function (event) {
+ var container = this.app.viewport.container;
+
+ event.preventDefault();
+
+ var array = this.getMousePosition(container.dom, event.clientX, event.clientY);
+ this.onDownPosition.fromArray(array);
+
+ document.addEventListener('mouseup', this.onMouseUp.bind(this), false);
+};
+
+PickEvent.prototype.onMouseUp = function (event) {
+ var container = this.app.viewport.container;
+
+ var array = this.getMousePosition(container.dom, event.clientX, event.clientY);
+ this.onUpPosition.fromArray(array);
+
+ this.handleClick();
+
+ document.removeEventListener('mouseup', this.onMouseUp, false);
+};
+
+PickEvent.prototype.onDoubleClick = function (event) {
+ var container = this.app.viewport.container;
+ var objects = this.app.editor.objects;
+
+ var array = this.getMousePosition(container.dom, event.clientX, event.clientY);
+ this.onDoubleClickPosition.fromArray(array);
+
+ var intersects = this.getIntersects(this.onDoubleClickPosition, objects);
+
+ if (intersects.length > 0) {
+ var intersect = intersects[0];
+ this.app.call('objectFocused', this, intersect.object);
+ }
+};
+
+PickEvent.prototype.getIntersects = function (point, objects) {
+ this.mouse.set((point.x * 2) - 1, -(point.y * 2) + 1);
+ this.raycaster.setFromCamera(this.mouse, this.app.editor.camera);
+ return this.raycaster.intersectObjects(objects);
+};
+
+PickEvent.prototype.getMousePosition = function (dom, x, y) {
+ var rect = dom.getBoundingClientRect();
+ return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
+};
+
+PickEvent.prototype.handleClick = function () {
+ var container = this.app.viewport.container;
+ var editor = this.app.editor;
+ var objects = editor.objects;
+
+ if (this.onDownPosition.distanceTo(this.onUpPosition) === 0) {
+ var intersects = this.getIntersects(this.onUpPosition, objects);
+
+ if (intersects.length > 0) {
+ var object = intersects[0].object;
+
+ if (object.userData.object !== undefined) {
+ // helper
+ editor.select(object.userData.object);
+ } else {
+ editor.select(object);
+ }
+ } else {
+ editor.select(null);
+ }
+
+ this.app.call('render');
+ }
+};
+
+export default PickEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/RenderEvent.js b/ShadowEditor.Core/src/event/RenderEvent.js
new file mode 100644
index 00000000..e4788739
--- /dev/null
+++ b/ShadowEditor.Core/src/event/RenderEvent.js
@@ -0,0 +1,101 @@
+import BaseEvent from './BaseEvent';
+import SelectEffect from '../effect/SelectEffect';
+
+/**
+ * 渲染事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function RenderEvent(app) {
+ BaseEvent.call(this, app);
+ // this.sceneSelected = new THREE.Scene();
+ this.selectEffect = new SelectEffect(this.app);
+}
+
+RenderEvent.prototype = Object.create(BaseEvent.prototype);
+RenderEvent.prototype.constructor = RenderEvent;
+
+RenderEvent.prototype.start = function () {
+ this.app.on(`render.${this.id}`, this.onRender.bind(this));
+ this.app.on(`materialChanged.${this.id}`, this.onRender.bind(this));
+ this.app.on(`sceneGraphChanged.${this.id}`, this.onRender.bind(this));
+ this.app.on(`cameraChanged.${this.id}`, this.onRender.bind(this));
+};
+
+RenderEvent.prototype.stop = function () {
+ this.app.on(`render.${this.id}`, null);
+ this.app.on(`materialChanged.${this.id}`, null);
+ this.app.on(`sceneGraphChanged.${this.id}`, null);
+ this.app.on(`cameraChanged.${this.id}`, null);
+};
+
+RenderEvent.prototype.onRender = function () {
+ var editor = this.app.editor;
+ var sceneHelpers = editor.sceneHelpers;
+ var scene = editor.scene;
+ var camera = editor.camera;
+ var renderer = editor.renderer;
+
+ sceneHelpers.updateMatrixWorld();
+ scene.updateMatrixWorld();
+
+ // 渲染场景
+ renderer.render(scene, camera);
+
+ // if (editor.selected && editor.selected instanceof THREE.Mesh) {
+ // this.selectEffect.render(editor.selected);
+ // }
+
+ // // 为选中的Mesh渲染边框
+ // if (editor.selected && editor.selected instanceof THREE.Mesh) {
+ // var box = new THREE.Mesh(editor.selected.geometry, new THREE.MeshBasicMaterial({
+ // color: 0xec651a,
+ // depthTest: false
+ // }));
+ // box.position.copy(editor.selected.position);
+ // box.rotation.copy(editor.selected.rotation);
+ // box.scale.copy(editor.selected.scale);
+
+ // var state = renderer.state;
+ // var context = renderer.context;
+
+ // this.sceneSelected.children.length = 0;
+ // this.sceneSelected.add(box);
+
+ // // 绘制模板
+ // renderer.clearStencil();
+ // state.buffers.stencil.setTest(true);
+ // state.buffers.stencil.setClear(0x00);
+
+ // state.buffers.color.setMask(false);
+ // state.buffers.stencil.setMask(0xff);
+ // state.buffers.stencil.setFunc(context.ALWAYS, 1, 0xff);
+
+ // state.buffers.color.setLocked(true);
+
+ // renderer.render(this.sceneSelected, camera);
+
+ // state.buffers.color.setLocked(false);
+ // state.buffers.color.setMask(true);
+ // state.buffers.stencil.setMask(0x00);
+
+ // // 绘制轮廓
+ // var oldScale = box.scale.clone();
+ // box.scale.set(oldScale.x * 1.1, oldScale.y * 1.1, oldScale.z * 1.1);
+
+ // state.buffers.stencil.setOp(context.KEEP, context.REPLACE, context.REPLACE);
+ // state.buffers.stencil.setFunc(context.NOTEQUAL, 1, 0xff);
+
+ // renderer.render(this.sceneSelected, camera);
+
+ // // 恢复原来状态
+ // box.scale.copy(oldScale);
+ // state.buffers.stencil.setTest(false);
+ // state.buffers.stencil.setOp(context.KEEP, context.KEEP, context.REPLACE);
+ // }
+
+ // 渲染帮助器
+ renderer.render(sceneHelpers, camera);
+};
+
+export default RenderEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/ResizeEvent.js b/ShadowEditor.Core/src/event/ResizeEvent.js
new file mode 100644
index 00000000..53423657
--- /dev/null
+++ b/ShadowEditor.Core/src/event/ResizeEvent.js
@@ -0,0 +1,40 @@
+import BaseEvent from './BaseEvent';
+
+/**
+ * 窗口大小改变事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function ResizeEvent(app) {
+ BaseEvent.call(this, app);
+}
+
+ResizeEvent.prototype = Object.create(BaseEvent.prototype);
+ResizeEvent.prototype.constructor = ResizeEvent;
+
+ResizeEvent.prototype.start = function () {
+ this.app.on(`resize.${this.id}`, this.onResize.bind(this));
+};
+
+ResizeEvent.prototype.stop = function () {
+ this.app.on(`resize.${this.id}`, null);
+};
+
+ResizeEvent.prototype.onResize = function () {
+ var editor = this.app.editor;
+ var container = this.app.viewport.container;
+ var camera = editor.camera;
+ var renderer = editor.renderer;
+
+ editor.DEFAULT_CAMERA.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
+ editor.DEFAULT_CAMERA.updateProjectionMatrix();
+
+ camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
+ camera.updateProjectionMatrix();
+
+ renderer.setSize(container.dom.offsetWidth, container.dom.offsetHeight);
+
+ this.app.call('render', this);
+};
+
+export default ResizeEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/TransformControlsEvent.js b/ShadowEditor.Core/src/event/TransformControlsEvent.js
new file mode 100644
index 00000000..d695a205
--- /dev/null
+++ b/ShadowEditor.Core/src/event/TransformControlsEvent.js
@@ -0,0 +1,174 @@
+import BaseEvent from './BaseEvent';
+import SetPositionCommand from './../command/SetPositionCommand';
+import SetRotationCommand from './../command/SetRotationCommand';
+import SetScaleCommand from './../command/SetScaleCommand';
+
+/**
+ * 平移旋转缩放控件事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function TransformControlsEvent(app) {
+ BaseEvent.call(this, app);
+
+ this.mode = 'translate';
+
+ this.objectPosition = null;
+ this.objectRotation = null;
+ this.objectScale = null;
+}
+
+TransformControlsEvent.prototype = Object.create(BaseEvent.prototype);
+TransformControlsEvent.prototype.constructor = TransformControlsEvent;
+
+TransformControlsEvent.prototype.start = function () {
+ var transformControls = this.app.editor.transformControls;
+
+ transformControls.addEventListener('change', this.onChange.bind(this));
+ transformControls.addEventListener('mouseDown', this.onMouseDown.bind(this));
+ transformControls.addEventListener('mouseUp', this.onMouseUp.bind(this));
+
+ this.app.on('objectSelected.' + this.id, this.onObjectSelected.bind(this));
+ this.app.on('changeMode.' + this.id, this.onChangeMode.bind(this));
+ this.app.on('snapChanged.' + this.id, this.onSnapChanged.bind(this));
+ this.app.on('spaceChanged.' + this.id, this.onSpaceChanged.bind(this));
+};
+
+TransformControlsEvent.prototype.stop = function () {
+ var transformControls = this.app.editor.transformControls;
+
+ transformControls.removeEventListener('change', this.onChange);
+ transformControls.removeEventListener('mouseDown', this.onMouseDown);
+ transformControls.removeEventListener('mouseUp', this.onMouseUp);
+
+ this.app.on('changeMode.' + this.id, null);
+ this.app.on('snapChanged.' + this.id, null);
+ this.app.on('spaceChanged.' + this.id, null);
+};
+
+/**
+ * 控件发生改变,需要更新包围盒位置,重绘场景
+ */
+TransformControlsEvent.prototype.onChange = function () {
+ var editor = this.app.editor;
+ var object = editor.transformControls.object;
+
+ if (object == null) {
+ this.app.call('render', this);
+ return;
+ }
+
+ if (editor.helpers[object.id] !== undefined && !(editor.helpers[object.id] instanceof THREE.SkeletonHelper)) {
+ editor.helpers[object.id].update();
+ }
+
+ this.app.call('objectChanged', this, object);
+ this.app.call('render');
+};
+
+/**
+ * 点击鼠标,记录选中物体当前平移、旋转和缩放值
+ */
+TransformControlsEvent.prototype.onMouseDown = function () {
+ if (['translate', 'rotate', 'scale'].indexOf(this.mode) === -1) {
+ return;
+ }
+
+ var object = this.app.editor.transformControls.object;
+
+ this.objectPosition = object.position.clone();
+ this.objectRotation = object.rotation.clone();
+ this.objectScale = object.scale.clone();
+
+ this.app.editor.controls.enabled = false; // EditorControls
+};
+
+/**
+ * 抬起鼠标,更新选中物体的平移、旋转和缩放值
+ */
+TransformControlsEvent.prototype.onMouseUp = function () {
+ if (['translate', 'rotate', 'scale'].indexOf(this.mode) === -1) {
+ return;
+ }
+
+ var editor = this.app.editor;
+ var transformControls = editor.transformControls;
+ var object = transformControls.object;
+
+ if (object == null) {
+ return;
+ }
+
+ switch (transformControls.getMode()) {
+ case 'translate':
+ if (!this.objectPosition.equals(object.position)) {
+ editor.execute(new SetPositionCommand(object, object.position, this.objectPosition));
+ }
+ break;
+ case 'rotate':
+ if (!this.objectRotation.equals(object.rotation)) {
+ editor.execute(new SetRotationCommand(object, object.rotation, this.objectRotation));
+ }
+ break;
+ case 'scale':
+ if (!this.objectScale.equals(object.scale)) {
+ editor.execute(new SetScaleCommand(object, object.scale, this.objectScale));
+ }
+ break;
+ }
+
+ this.app.editor.controls.enabled = true; // EditorControls
+};
+
+/**
+ * 物体已经选中
+ * @param {*} object 选中的物体
+ */
+TransformControlsEvent.prototype.onObjectSelected = function (object) {
+ this.app.editor.transformControls.detach();
+
+ if (['translate', 'rotate', 'scale'].indexOf(this.mode) === -1) {
+ return;
+ }
+
+ if (object && !(object instanceof THREE.Scene) && !(object instanceof THREE.PerspectiveCamera && object.userData.isDefault === true)) {
+ this.app.editor.transformControls.attach(object);
+ }
+};
+
+/**
+ * 切换平移、旋转、缩放模式
+ * @param {*} mode 模式
+ */
+TransformControlsEvent.prototype.onChangeMode = function (mode) {
+ this.mode = mode;
+ var transformControls = this.app.editor.transformControls;
+
+ if (mode === 'translate' || mode === 'rotate' || mode === 'scale') { // 设置模式在选中物体上
+ transformControls.setMode(mode);
+ var object = this.app.editor.selected;
+ if (object != null) {
+ transformControls.attach(object);
+ }
+ } else { // 取消对选中物体平移、旋转、缩放
+ transformControls.detach();
+ }
+};
+
+/**
+ * 设置平移移动的大小
+ * @param {*} dist
+ */
+TransformControlsEvent.prototype.onSnapChanged = function (dist) {
+ this.app.editor.transformControls.setTranslationSnap(dist);
+};
+
+/**
+ * 设置世界坐标系还是物体坐标系
+ * @param {*} space
+ */
+TransformControlsEvent.prototype.onSpaceChanged = function (space) {
+ this.app.editor.transformControls.setSpace(space);
+};
+
+export default TransformControlsEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/menu/MenuEvent.js b/ShadowEditor.Core/src/event/menu/MenuEvent.js
new file mode 100644
index 00000000..6b77f39b
--- /dev/null
+++ b/ShadowEditor.Core/src/event/menu/MenuEvent.js
@@ -0,0 +1,23 @@
+import BaseEvent from '../BaseEvent';
+
+/**
+ * 菜单事件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function MenuEvent(app) {
+ BaseEvent.call(this, app);
+}
+
+MenuEvent.prototype = Object.create(BaseEvent.prototype);
+MenuEvent.prototype.constructor = MenuEvent;
+
+MenuEvent.prototype.start = function () {
+
+};
+
+MenuEvent.prototype.stop = function () {
+
+};
+
+export default MenuEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/menu/physics/AddPhysicsClothEvent.js b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsClothEvent.js
new file mode 100644
index 00000000..e418f9e8
--- /dev/null
+++ b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsClothEvent.js
@@ -0,0 +1,145 @@
+import MenuEvent from '../MenuEvent';
+import AddObjectCommand from '../../../command/AddObjectCommand';
+
+/**
+ * 添加物理布料
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function AddPhysicsClothEvent(app) {
+ MenuEvent.call(this, app);
+}
+
+AddPhysicsClothEvent.prototype = Object.create(MenuEvent.prototype);
+AddPhysicsClothEvent.prototype.constructor = AddPhysicsClothEvent;
+
+AddPhysicsClothEvent.prototype.start = function () {
+ this.app.on(`mAddPhysicsCloth.${this.id}`, this.onAddCloth.bind(this));
+};
+
+AddPhysicsClothEvent.prototype.stop = function () {
+ this.app.on(`mAddPhysicsCloth.${this.id}`, null);
+};
+
+AddPhysicsClothEvent.prototype.onAddCloth = function () {
+ // The cloth
+ // Cloth graphic object
+ var clothWidth = 4;
+ var clothHeight = 3;
+ var clothNumSegmentsZ = clothWidth * 5;
+ var clothNumSegmentsY = clothHeight * 5;
+ var clothSegmentLengthZ = clothWidth / clothNumSegmentsZ;
+ var clothSegmentLengthY = clothHeight / clothNumSegmentsY;
+ var clothPos = new THREE.Vector3(-3, 3, 2);
+ //var clothGeometry = new THREE.BufferGeometry();
+ var clothGeometry = new THREE.PlaneBufferGeometry(clothWidth, clothHeight, clothNumSegmentsZ, clothNumSegmentsY);
+ clothGeometry.rotateY(Math.PI * 0.5);
+ clothGeometry.translate(clothPos.x, clothPos.y + clothHeight * 0.5, clothPos.z - clothWidth * 0.5);
+ //var clothMaterial = new THREE.MeshLambertMaterial( { color: 0x0030A0, side: THREE.DoubleSide } );
+ var clothMaterial = new THREE.MeshLambertMaterial({ color: 0xFFFFFF, side: THREE.DoubleSide });
+ var cloth = new THREE.Mesh(clothGeometry, clothMaterial);
+ cloth.name = '布';
+ cloth.castShadow = true;
+ cloth.receiveShadow = false;
+
+ this.app.editor.execute(new AddObjectCommand(cloth));
+
+ new THREE.TextureLoader().load("assets/textures/grid.png", function (texture) {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(clothNumSegmentsZ, clothNumSegmentsY);
+ cloth.material.map = texture;
+ cloth.material.needsUpdate = true;
+ });
+ // Cloth physic object
+ var softBodyHelpers = new Ammo.btSoftBodyHelpers();
+ var clothCorner00 = new Ammo.btVector3(clothPos.x, clothPos.y + clothHeight, clothPos.z);
+ var clothCorner01 = new Ammo.btVector3(clothPos.x, clothPos.y + clothHeight, clothPos.z - clothWidth);
+ var clothCorner10 = new Ammo.btVector3(clothPos.x, clothPos.y, clothPos.z);
+ var clothCorner11 = new Ammo.btVector3(clothPos.x, clothPos.y, clothPos.z - clothWidth);
+ var clothSoftBody = softBodyHelpers.CreatePatch(this.app.physics.world.getWorldInfo(), clothCorner00, clothCorner01, clothCorner10, clothCorner11, clothNumSegmentsZ + 1, clothNumSegmentsY + 1, 0, true);
+ var sbConfig = clothSoftBody.get_m_cfg();
+ sbConfig.set_viterations(10);
+ sbConfig.set_piterations(10);
+ clothSoftBody.setTotalMass(0.9, false);
+ Ammo.castObject(clothSoftBody, Ammo.btCollisionObject).getCollisionShape().setMargin(this.app.physics.margin * 3);
+
+ this.app.physics.world.addSoftBody(clothSoftBody, 1, -1);
+
+ cloth.userData.physicsBody = clothSoftBody;
+ // Disable deactivation
+ clothSoftBody.setActivationState(4);
+
+ // The base
+ var armMass = 2;
+ var armLength = 3 + clothWidth;
+ var pylonHeight = clothPos.y + clothHeight;
+ var baseMaterial = new THREE.MeshPhongMaterial({ color: 0x606060 });
+ var pos = new THREE.Vector3();
+ pos.set(clothPos.x, 0.1, clothPos.z - armLength);
+ var quat = new THREE.Quaternion();
+ quat.set(0, 0, 0, 1);
+
+ var base = this.app.physics.createParalellepiped(1, 0.2, 1, 0, pos, quat, baseMaterial);
+ base.castShadow = true;
+ base.receiveShadow = true;
+ pos.set(clothPos.x, 0.5 * pylonHeight, clothPos.z - armLength);
+
+ this.app.editor.execute(new AddObjectCommand(base));
+
+ var pylon = this.app.physics.createParalellepiped(0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial);
+ pylon.castShadow = true;
+ pylon.receiveShadow = true;
+ pos.set(clothPos.x, pylonHeight + 0.2, clothPos.z - 0.5 * armLength);
+
+ this.app.editor.execute(new AddObjectCommand(pylon));
+
+ var arm = this.app.physics.createParalellepiped(0.4, 0.4, armLength + 0.4, 0, pos, quat, baseMaterial);
+ arm.castShadow = true;
+ arm.receiveShadow = true;
+
+ this.app.editor.execute(new AddObjectCommand(arm));
+
+ // Glue the cloth to the arm
+ var influence = 0.5;
+ clothSoftBody.appendAnchor(0, arm.userData.physicsBody, false, influence);
+ clothSoftBody.appendAnchor(clothNumSegmentsZ, arm.userData.physicsBody, false, influence);
+
+ // Hinge constraint to move the arm
+ var pivotA = new Ammo.btVector3(0, pylonHeight * 0.5, 0);
+ var pivotB = new Ammo.btVector3(0, -0.2, - armLength * 0.5);
+ var axis = new Ammo.btVector3(0, 1, 0);
+ var hinge = new Ammo.btHingeConstraint(pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true);
+ this.app.physics.world.addConstraint(hinge, true);
+
+ this.cloth = cloth;
+
+ this.app.on(`animate`, this.onAnimate.bind(this));
+};
+
+AddPhysicsClothEvent.prototype.onAnimate = function (clock, deltaTime) {
+ if (this.cloth == null) {
+ return;
+ }
+
+ var cloth = this.cloth;
+
+ // Update cloth
+ var softBody = cloth.userData.physicsBody;
+ var clothPositions = cloth.geometry.attributes.position.array;
+ var numVerts = clothPositions.length / 3;
+ var nodes = softBody.get_m_nodes();
+ var indexFloat = 0;
+ for (var i = 0; i < numVerts; i++) {
+ var node = nodes.at(i);
+ var nodePos = node.get_m_x();
+ clothPositions[indexFloat++] = nodePos.x();
+ clothPositions[indexFloat++] = nodePos.y();
+ clothPositions[indexFloat++] = nodePos.z();
+ }
+ cloth.geometry.computeVertexNormals();
+ cloth.geometry.attributes.position.needsUpdate = true;
+ cloth.geometry.attributes.normal.needsUpdate = true;
+};
+
+export default AddPhysicsClothEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/menu/physics/AddPhysicsPlaneEvent.js b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsPlaneEvent.js
new file mode 100644
index 00000000..016564b9
--- /dev/null
+++ b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsPlaneEvent.js
@@ -0,0 +1,45 @@
+import MenuEvent from '../MenuEvent';
+import AddObjectCommand from '../../../command/AddObjectCommand';
+
+/**
+ * 添加物理平板
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function AddPhysicsPlaneEvent(app) {
+ MenuEvent.call(this, app);
+}
+
+AddPhysicsPlaneEvent.prototype = Object.create(MenuEvent.prototype);
+AddPhysicsPlaneEvent.prototype.constructor = AddPhysicsPlaneEvent;
+
+AddPhysicsPlaneEvent.prototype.start = function () {
+ this.app.on(`mAddPhysicsPlane.${this.id}`, this.onAddPlane.bind(this));
+};
+
+AddPhysicsPlaneEvent.prototype.stop = function () {
+ this.app.on(`mAddPhysicsPlane.${this.id}`, null);
+};
+
+AddPhysicsPlaneEvent.prototype.onAddPlane = function () {
+ var pos = new THREE.Vector3(0, -0.5, 0);
+ var quat = new THREE.Quaternion();
+
+ var ground = this.app.physics.createParalellepiped(40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial({ color: 0xFFFFFF }));
+ ground.name = '物理平板';
+ ground.castShadow = true;
+ ground.receiveShadow = true;
+
+ this.app.editor.execute(new AddObjectCommand(ground));
+
+ var loader = new THREE.TextureLoader();
+ loader.load("assets/textures/grid.png", function (texture) {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(40, 40);
+ ground.material.map = texture;
+ ground.material.needsUpdate = true;
+ });
+};
+
+export default AddPhysicsPlaneEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/event/menu/physics/AddPhysicsWallEvent.js b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsWallEvent.js
new file mode 100644
index 00000000..7874e9ee
--- /dev/null
+++ b/ShadowEditor.Core/src/event/menu/physics/AddPhysicsWallEvent.js
@@ -0,0 +1,72 @@
+import MenuEvent from '../MenuEvent';
+import AddObjectCommand from '../../../command/AddObjectCommand';
+
+/**
+ * 添加物理墙
+ * @author tengge / https://github.com/tengge1
+ * @param {*} app
+ */
+function AddPhysicsWallEvent(app) {
+ MenuEvent.call(this, app);
+}
+
+AddPhysicsWallEvent.prototype = Object.create(MenuEvent.prototype);
+AddPhysicsWallEvent.prototype.constructor = AddPhysicsWallEvent;
+
+AddPhysicsWallEvent.prototype.start = function () {
+ this.app.on(`mAddPhysicsWall.${this.id}`, this.onAddWall.bind(this));
+};
+
+AddPhysicsWallEvent.prototype.stop = function () {
+ this.app.on(`mAddPhysicsWall.${this.id}`, null);
+};
+
+AddPhysicsWallEvent.prototype.onAddWall = function () {
+ var editor = this.app.editor;
+
+ var pos = new THREE.Vector3();
+ var quat = new THREE.Quaternion();
+
+ // Wall
+ var brickMass = 0.5;
+ var brickLength = 1.2;
+ var brickDepth = 0.6;
+ var brickHeight = brickLength * 0.5;
+ var numBricksLength = 6;
+ var numBricksHeight = 8;
+ var z0 = - numBricksLength * brickLength * 0.5;
+ pos.set(0, brickHeight * 0.5, z0);
+ quat.set(0, 0, 0, 1);
+ for (var j = 0; j < numBricksHeight; j++) {
+ var oddRow = (j % 2) == 1;
+ pos.z = z0;
+ if (oddRow) {
+ pos.z -= 0.25 * brickLength;
+ }
+ var nRow = oddRow ? numBricksLength + 1 : numBricksLength;
+ for (var i = 0; i < nRow; i++) {
+ var brickLengthCurrent = brickLength;
+ var brickMassCurrent = brickMass;
+ if (oddRow && (i == 0 || i == nRow - 1)) {
+ brickLengthCurrent *= 0.5;
+ brickMassCurrent *= 0.5;
+ }
+ var color = Math.floor(Math.random() * (1 << 24));
+ var material = new THREE.MeshPhongMaterial({ color: color });
+ var brick = this.app.physics.createParalellepiped(brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, material);
+ brick.castShadow = true;
+ brick.receiveShadow = true;
+ editor.scene.add(brick);
+
+ if (oddRow && (i == 0 || i == nRow - 2)) {
+ pos.z += 0.75 * brickLength;
+ }
+ else {
+ pos.z += brickLength;
+ }
+ }
+ pos.y += brickHeight;
+ }
+};
+
+export default AddPhysicsWallEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/index.js b/ShadowEditor.Core/src/index.js
new file mode 100644
index 00000000..21c5a6ab
--- /dev/null
+++ b/ShadowEditor.Core/src/index.js
@@ -0,0 +1,6 @@
+import './ui/ImageListWindow';
+import './ui/ImageUploader';
+import './ui/Outliner';
+
+export { default as Options } from './Options';
+export { default as Application } from './Application';
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/AMFLoader.js b/ShadowEditor.Core/src/loader/AMFLoader.js
new file mode 100644
index 00000000..753db314
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/AMFLoader.js
@@ -0,0 +1,25 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * AMFLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function AMFLoader() {
+ BaseLoader.call(this);
+}
+
+AMFLoader.prototype = Object.create(BaseLoader.prototype);
+AMFLoader.prototype.constructor = AMFLoader;
+
+AMFLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.AMFLoader();
+ loader.load(url, (group) => {
+ resolve(group);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default AMFLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/AWDLoader.js b/ShadowEditor.Core/src/loader/AWDLoader.js
new file mode 100644
index 00000000..74cb6c00
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/AWDLoader.js
@@ -0,0 +1,26 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * AWDLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function AWDLoader() {
+ BaseLoader.call(this);
+}
+
+AWDLoader.prototype = Object.create(BaseLoader.prototype);
+AWDLoader.prototype.constructor = AWDLoader;
+
+AWDLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.AWDLoader();
+
+ loader.load(url, (obj3d) => {
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default AWDLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/BabylonLoader.js b/ShadowEditor.Core/src/loader/BabylonLoader.js
new file mode 100644
index 00000000..eb43d397
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/BabylonLoader.js
@@ -0,0 +1,28 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * BabylonLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function BabylonLoader() {
+ BaseLoader.call(this);
+}
+
+BabylonLoader.prototype = Object.create(BaseLoader.prototype);
+BabylonLoader.prototype.constructor = BabylonLoader;
+
+BabylonLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.BabylonLoader();
+
+ loader.load(url, (scene) => {
+ var obj3d = new THREE.Object3D();
+ obj3d.children = scene.children;
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default BabylonLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/BaseLoader.js b/ShadowEditor.Core/src/loader/BaseLoader.js
new file mode 100644
index 00000000..8ae3374c
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/BaseLoader.js
@@ -0,0 +1,17 @@
+var ID = -1;
+
+/**
+ * BaseLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function BaseLoader() {
+ this.id = `BaseLoader${ID--}`;
+}
+
+BaseLoader.prototype.load = function (url, options) {
+ return new Promise(resolve => {
+ resolve(null);
+ });
+};
+
+export default BaseLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/BinaryLoader.js b/ShadowEditor.Core/src/loader/BinaryLoader.js
new file mode 100644
index 00000000..c021fec6
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/BinaryLoader.js
@@ -0,0 +1,27 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * BinaryLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function BinaryLoader() {
+ BaseLoader.call(this);
+}
+
+BinaryLoader.prototype = Object.create(BaseLoader.prototype);
+BinaryLoader.prototype.constructor = BinaryLoader;
+
+BinaryLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.BinaryLoader();
+
+ loader.load(url, (geometry, materials) => {
+ var mesh = new THREE.Mesh(geometry, materials);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default BinaryLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/CTMLoader.js b/ShadowEditor.Core/src/loader/CTMLoader.js
new file mode 100644
index 00000000..fc1d085c
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/CTMLoader.js
@@ -0,0 +1,28 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * CTMLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function CTMLoader() {
+ BaseLoader.call(this);
+}
+
+CTMLoader.prototype = Object.create(BaseLoader.prototype);
+CTMLoader.prototype.constructor = CTMLoader;
+
+CTMLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.CTMLoader();
+
+ loader.load(url, geometry => {
+ var material = new THREE.MeshStandardMaterial();
+ var mesh = new THREE.Mesh(geometry, material);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default CTMLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/ColladaLoader.js b/ShadowEditor.Core/src/loader/ColladaLoader.js
new file mode 100644
index 00000000..9f570f9a
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/ColladaLoader.js
@@ -0,0 +1,27 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * ColladaLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function ColladaLoader() {
+ BaseLoader.call(this);
+}
+
+ColladaLoader.prototype = Object.create(BaseLoader.prototype);
+ColladaLoader.prototype.constructor = ColladaLoader;
+
+ColladaLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.ColladaLoader();
+
+ loader.load(url, collada => {
+ var obj3d = collada.scene;
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default ColladaLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/FBXLoader.js b/ShadowEditor.Core/src/loader/FBXLoader.js
new file mode 100644
index 00000000..3a78a59e
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/FBXLoader.js
@@ -0,0 +1,26 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * FBXLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function FBXLoader() {
+ BaseLoader.call(this);
+}
+
+FBXLoader.prototype = Object.create(BaseLoader.prototype);
+FBXLoader.prototype.constructor = FBXLoader;
+
+FBXLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.FBXLoader();
+
+ loader.load(url, obj3d => {
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default FBXLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/GLTFLoader.js b/ShadowEditor.Core/src/loader/GLTFLoader.js
new file mode 100644
index 00000000..a900d5b8
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/GLTFLoader.js
@@ -0,0 +1,27 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * GLTFLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function GLTFLoader() {
+ BaseLoader.call(this);
+}
+
+GLTFLoader.prototype = Object.create(BaseLoader.prototype);
+GLTFLoader.prototype.constructor = GLTFLoader;
+
+GLTFLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.GLTFLoader();
+
+ loader.load(url, result => {
+ var obj3d = result.scene;
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default GLTFLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/KMZLoader.js b/ShadowEditor.Core/src/loader/KMZLoader.js
new file mode 100644
index 00000000..b4250627
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/KMZLoader.js
@@ -0,0 +1,27 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * KMZLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function KMZLoader() {
+ BaseLoader.call(this);
+}
+
+KMZLoader.prototype = Object.create(BaseLoader.prototype);
+KMZLoader.prototype.constructor = KMZLoader;
+
+KMZLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.KMZLoader();
+
+ loader.load(url, collada => {
+ var obj3d = collada.scene;
+ resolve(obj3d);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default KMZLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/LOLLoader.js b/ShadowEditor.Core/src/loader/LOLLoader.js
new file mode 100644
index 00000000..d5dd2a53
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/LOLLoader.js
@@ -0,0 +1,94 @@
+import BaseLoader from './BaseLoader';
+import LolModel from '../lol/Model';
+
+/**
+ * LOLLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function LOLLoader() {
+ BaseLoader.call(this);
+}
+
+LOLLoader.prototype = Object.create(BaseLoader.prototype);
+LOLLoader.prototype.constructor = LOLLoader;
+
+LOLLoader.prototype.load = function (url, options) {
+ if (!Array.isArray(url) || url.length < 3) {
+ console.warn(`LOLLoader: url必须是数组,而且包含.lmesh、.lanim、.png三个文件地址。`);
+ return new Promise(resolve => {
+ resolve(null);
+ });
+ }
+
+ var lmesh = url.filter(n => n.endsWith('.lmesh'))[0];
+ var lanim = url.filter(n => n.endsWith('.lanim'))[0];
+ var png = url.filter(n => n.endsWith('.png'))[0];
+
+ if (lmesh === undefined) {
+ console.warn(`LOLLoader: url中不包含.lmesh文件地址。`);
+ return new Promise(resolve => {
+ resolve(null);
+ });
+ }
+
+ if (lanim === undefined) {
+ console.warn(`LOLLoader: url中不包含.lanim文件地址。`);
+ return new Promise(resolve => {
+ resolve(null);
+ });
+ }
+
+ if (png === undefined) {
+ console.warn(`LOLLoader: url中不包含.png文件地址。`);
+ return new Promise(resolve => {
+ resolve(null);
+ });
+ }
+
+ var fileName = lmesh.split('/')[lmesh.split('/').length - 1];
+ var fileNameNoExt = fileName.split('.')[0];
+ var champion = fileNameNoExt.split('_')[0];
+ var skin = fileNameNoExt.split('_')[1];
+
+ return new Promise(resolve => {
+ var model = new LolModel({
+ champion: champion,
+ skin: parseInt(skin),
+ meshUrl: lmesh,
+ animUrl: lanim,
+ textureUrl: png
+ });
+ model.load();
+ model.on('load.LOLLoader', () => {
+ var geometry = model.geometry;
+ var material = model.material;
+
+ var mesh = new THREE.Mesh(geometry, material);
+ mesh.name = options.Name;
+
+ mesh.userData.type = 'lol';
+ mesh.userData.model = model;
+ mesh.userData.scripts = [{
+ id: null,
+ name: `${options.Name}动画`,
+ type: 'javascript',
+ source: this.createScripts(options.Name, model),
+ uuid: THREE.Math.generateUUID()
+ }];
+
+ resolve(mesh);
+ });
+ });
+};
+
+LOLLoader.prototype.createScripts = function (name, model) {
+ var animations = model.getAnimations();
+
+ return `var mesh = this.getObjectByName('${name}');\n` +
+ `var model = mesh.userData.model;\n\n` +
+ `// animNames: ${animations.join(',')}\n` +
+ `model.setAnimation('${animations[0]}');\n\n` +
+ `function update(clock, deltaTime) { \n model.update(clock.getElapsedTime() * 1000); \n}`;
+};
+
+export default LOLLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/MD2Loader.js b/ShadowEditor.Core/src/loader/MD2Loader.js
new file mode 100644
index 00000000..b78f1cb4
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/MD2Loader.js
@@ -0,0 +1,34 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * MD2Loader
+ * @author tengge / https://github.com/tengge1
+ */
+function MD2Loader() {
+ BaseLoader.call(this);
+}
+
+MD2Loader.prototype = Object.create(BaseLoader.prototype);
+MD2Loader.prototype.constructor = MD2Loader;
+
+MD2Loader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.MD2Loader();
+
+ loader.load(url, geometry => {
+ var material = new THREE.MeshStandardMaterial({
+ morphTargets: true,
+ morphNormals: true
+ });
+
+ var mesh = new THREE.Mesh(geometry, material);
+ mesh.mixer = new THREE.AnimationMixer(mesh);
+
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default MD2Loader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/ModelLoader.js b/ShadowEditor.Core/src/loader/ModelLoader.js
new file mode 100644
index 00000000..07186c13
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/ModelLoader.js
@@ -0,0 +1,77 @@
+import BaseLoader from './BaseLoader';
+import AMFLoader from './AMFLoader';
+import AWDLoader from './AWDLoader';
+import BabylonLoader from './BabylonLoader';
+import BinaryLoader from './BinaryLoader';
+import ColladaLoader from './ColladaLoader';
+import CTMLoader from './CTMLoader';
+import FBXLoader from './FBXLoader';
+import GLTFLoader from './GLTFLoader';
+import KMZLoader from './KMZLoader';
+import MD2Loader from './MD2Loader';
+import ObjectLoader from './ObjectLoader';
+import OBJLoader from './OBJLoader';
+import PLYLoader from './PLYLoader';
+import STLLoader from './STLLoader';
+import VTKLoader from './VTKLoader';
+import LOLLoader from './LOLLoader';
+import PMDLoader from './PMDLoader';
+
+const Loaders = {
+ 'amf': AMFLoader,
+ 'awd': AWDLoader,
+ 'babylon': BabylonLoader,
+ 'binary': BinaryLoader,
+ 'ctm': CTMLoader,
+ 'dae': ColladaLoader,
+ 'fbx': FBXLoader,
+ 'glb': GLTFLoader,
+ 'gltf': GLTFLoader,
+ 'kmz': KMZLoader,
+ 'md2': MD2Loader,
+ 'json': ObjectLoader,
+ 'obj': OBJLoader,
+ 'ply': PLYLoader,
+ 'stl': STLLoader,
+ 'vtk': VTKLoader,
+ 'lol': LOLLoader,
+ 'pmd': PMDLoader,
+ 'pmx': PMDLoader,
+};
+
+/**
+ * ModelLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function ModelLoader() {
+ BaseLoader.call(this);
+}
+
+ModelLoader.prototype = Object.create(BaseLoader.prototype);
+ModelLoader.prototype.constructor = ModelLoader;
+
+ModelLoader.prototype.load = function (url, options) {
+ options = options || {};
+ var type = options.Type;
+
+ if (type === undefined) {
+ console.warn(`ModelLoader: 未传递type参数,无法加载。`);
+ return new Promise(resolve => {
+ resolve(null);
+ });
+ }
+
+ return new Promise(resolve => {
+ var loader = Loaders[type];
+ if (loader === undefined) {
+ console.warn(`ModelLoader: 不存在加载${type}后缀模型的加载器。`);
+ resolve(null);
+ return;
+ }
+ (new loader(this.app)).load(url, options).then(obj => {
+ resolve(obj);
+ });
+ });
+};
+
+export default ModelLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/OBJLoader.js b/ShadowEditor.Core/src/loader/OBJLoader.js
new file mode 100644
index 00000000..7bd2188e
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/OBJLoader.js
@@ -0,0 +1,26 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * OBJLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function OBJLoader() {
+ BaseLoader.call(this);
+}
+
+OBJLoader.prototype = Object.create(BaseLoader.prototype);
+OBJLoader.prototype.constructor = OBJLoader;
+
+OBJLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.OBJLoader();
+
+ loader.load(url, obj => {
+ resolve(obj);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default OBJLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/ObjectLoader.js b/ShadowEditor.Core/src/loader/ObjectLoader.js
new file mode 100644
index 00000000..b3596b2d
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/ObjectLoader.js
@@ -0,0 +1,71 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * ObjectLoader(json文件加载器)
+ * @author tengge / https://github.com/tengge1
+ */
+function ObjectLoader() {
+ BaseLoader.call(this);
+}
+
+ObjectLoader.prototype = Object.create(BaseLoader.prototype);
+ObjectLoader.prototype.constructor = ObjectLoader;
+
+ObjectLoader.prototype.load = function (url, options) {
+ return new Promise(resolve => {
+ var loader = new THREE.ObjectLoader();
+
+ loader.load(url, obj => {
+ if (obj instanceof THREE.Scene && obj.children.length > 0 && obj.children[0] instanceof THREE.SkinnedMesh) {
+ resolve(this.loadSkinnedMesh(obj, options));
+ } else {
+ resolve(obj);
+ }
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+ObjectLoader.prototype.loadSkinnedMesh = function (scene, options) {
+ var mesh = null;
+
+ scene.traverse(child => {
+ if (child instanceof THREE.SkinnedMesh) {
+ mesh = child;
+ }
+ });
+
+ var animations = mesh.geometry.animations;
+
+ if (options.Name && animations && animations.length > 0) {
+
+ var names = animations.map(n => n.name);
+
+ var source1 = `var mesh = this.getObjectByName('${options.Name}');\nvar mixer = new THREE.AnimationMixer(mesh);\n\n`;
+
+ var source2 = ``;
+
+ names.forEach(n => {
+ source2 += `var ${n}Animation = mixer.clipAction('${n}');\n`;
+ });
+
+ var source3 = `\n${names[0]}Animation.play();\n\n`;
+
+ var source4 = `function update(clock, deltaTime) { \n mixer.update(deltaTime); \n}`;
+
+ var source = source1 + source2 + source3 + source4;
+
+ mesh.userData.scripts = [{
+ id: null,
+ name: `${options.Name}动画`,
+ type: 'javascript',
+ source: source,
+ uuid: THREE.Math.generateUUID()
+ }];
+ }
+
+ return mesh;
+};
+
+export default ObjectLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/PLYLoader.js b/ShadowEditor.Core/src/loader/PLYLoader.js
new file mode 100644
index 00000000..5b7758a7
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/PLYLoader.js
@@ -0,0 +1,28 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * PLYLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function PLYLoader() {
+ BaseLoader.call(this);
+}
+
+PLYLoader.prototype = Object.create(BaseLoader.prototype);
+PLYLoader.prototype.constructor = PLYLoader;
+
+PLYLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.PLYLoader();
+
+ loader.load(url, geometry => {
+ var material = new THREE.MeshStandardMaterial();
+ var mesh = new THREE.Mesh(geometry, material);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default PLYLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/PMDLoader.js b/ShadowEditor.Core/src/loader/PMDLoader.js
new file mode 100644
index 00000000..13498212
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/PMDLoader.js
@@ -0,0 +1,46 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * PMDLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function PMDLoader() {
+ BaseLoader.call(this);
+}
+
+PMDLoader.prototype = Object.create(BaseLoader.prototype);
+PMDLoader.prototype.constructor = PMDLoader;
+
+PMDLoader.prototype.load = function (url, options) {
+ return new Promise(resolve => {
+ var loader = new THREE.MMDLoader();
+
+ loader.loadModel(url, mesh => {
+ mesh.name = options.Name;
+ if (options.Animation) {
+ this.loadAnimation(mesh, options.Animation).then(() => {
+ resolve(mesh);
+ });
+ } else {
+ resolve(mesh);
+ }
+ }, undefined, () => {
+ // 某个图片下载失败会导致返回null
+ // resolve(null);
+ });
+ });
+};
+
+PMDLoader.prototype.loadAnimation = function (mesh, animation) {
+ return new Promise(resolve => {
+ var loader = new THREE.MMDLoader();
+ loader.loadVmd(animation.Url, vmd => {
+ loader.pourVmdIntoModel(mesh, vmd);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default PMDLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/STLLoader.js b/ShadowEditor.Core/src/loader/STLLoader.js
new file mode 100644
index 00000000..81853bb6
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/STLLoader.js
@@ -0,0 +1,28 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * STLLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function STLLoader() {
+ BaseLoader.call(this);
+}
+
+STLLoader.prototype = Object.create(BaseLoader.prototype);
+STLLoader.prototype.constructor = STLLoader;
+
+STLLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.STLLoader();
+
+ loader.load(url, geometry => {
+ var material = new THREE.MeshStandardMaterial();
+ var mesh = new THREE.Mesh(geometry, material);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default STLLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/loader/VTKLoader.js b/ShadowEditor.Core/src/loader/VTKLoader.js
new file mode 100644
index 00000000..b55701c4
--- /dev/null
+++ b/ShadowEditor.Core/src/loader/VTKLoader.js
@@ -0,0 +1,28 @@
+import BaseLoader from './BaseLoader';
+
+/**
+ * VTKLoader
+ * @author tengge / https://github.com/tengge1
+ */
+function VTKLoader() {
+ BaseLoader.call(this);
+}
+
+VTKLoader.prototype = Object.create(BaseLoader.prototype);
+VTKLoader.prototype.constructor = VTKLoader;
+
+VTKLoader.prototype.load = function (url) {
+ return new Promise(resolve => {
+ var loader = new THREE.VTKLoader();
+
+ loader.load(url, geometry => {
+ var material = new THREE.MeshStandardMaterial();
+ var mesh = new THREE.Mesh(geometry, material);
+ resolve(mesh);
+ }, undefined, () => {
+ resolve(null);
+ });
+ });
+};
+
+export default VTKLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Animation.js b/ShadowEditor.Core/src/lol/Animation.js
new file mode 100644
index 00000000..8b8a85c1
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Animation.js
@@ -0,0 +1,28 @@
+import AnimationBone from './AnimationBone';
+
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function Animation(model, r, version) {
+ var self = this,
+ i;
+ self.model = model;
+ self.meshOverride = {};
+ self.name = r.getString().toLowerCase();
+ self.fps = r.getInt32();
+ var numBones = r.getUint32();
+ self.bones = new Array(numBones);
+ self.lookup = {};
+ for (i = 0; i < numBones; ++i) {
+ self.bones[i] = new AnimationBone(model, self, r, version);
+ self.lookup[self.bones[i].bone] = i
+ }
+ if (numBones == 0 || self.fps <= 1) {
+ self.duration = 1e3
+ } else {
+ self.duration = Math.floor(1e3 * (self.bones[0].frames.length / self.fps))
+ }
+};
+
+export default Animation;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/AnimationBone.js b/ShadowEditor.Core/src/lol/AnimationBone.js
new file mode 100644
index 00000000..d967d936
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/AnimationBone.js
@@ -0,0 +1,137 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function AnimationBone(model, anim, r, version) {
+ var self = this;
+ self.model = model;
+ self.anim = anim;
+ var numFrames = r.getUint32();
+ self.bone = r.getString().toLowerCase();
+ self.flags = r.getUint32();
+ self.frames = new Array(numFrames);
+ var scale = [1, 1, 1];
+ for (var i = 0; i < numFrames; ++i) {
+ var pos = [r.getFloat(), r.getFloat(), r.getFloat()];
+ var rot = [r.getFloat(), r.getFloat(), r.getFloat(), r.getFloat()];
+ if (version >= 3) scale = [r.getFloat(), r.getFloat(), r.getFloat()];
+ self.frames[i] = {
+ pos: pos,
+ rot: rot,
+ scale: scale
+ }
+ }
+ self.matrix = mat4.create();
+ self.tmpMat = mat4.create();
+ self.tmpMat2 = mat4.create();
+ self.tmpPos = vec3.create();
+ self.tmpRot = quat.create();
+ self.tmpScale = vec3.create()
+};
+
+AnimationBone.prototype.update = function (boneId, frame, r) {
+ var self = this;
+ self.index = boneId;
+ var parent = self.model.bones[boneId].parent;
+ var f0 = frame % self.frames.length,
+ f1 = (frame + 1) % self.frames.length;
+ vec3.lerp(self.tmpPos, self.frames[f0].pos, self.frames[f1].pos, r);
+ vec3.lerp(self.tmpScale, self.frames[f0].scale, self.frames[f1].scale, r);
+ quat.slerp(self.tmpRot, self.frames[f0].rot, self.frames[f1].rot, r);
+ self.translation(self.tmpMat2, self.tmpPos);
+ self.rotationQuat(self.tmpMat, self.tmpRot);
+ self.mulSlimDX(self.matrix, self.tmpMat, self.tmpMat2);
+ if (parent != -1) {
+ self.mulSlimDX(self.matrix, self.matrix, self.model.transforms[parent])
+ }
+ mat4.copy(self.model.transforms[boneId], self.matrix)
+};
+
+AnimationBone.prototype.translation = function (out, vec) {
+ mat4.identity(out);
+ out[12] = vec[0];
+ out[13] = vec[1];
+ out[14] = vec[2];
+ return out
+};
+
+AnimationBone.prototype.rotationQuat = function (out, q) {
+ mat4.identity(out);
+ var xx = q[0] * q[0],
+ yy = q[1] * q[1],
+ zz = q[2] * q[2],
+ xy = q[0] * q[1],
+ zw = q[2] * q[3],
+ zx = q[2] * q[0],
+ yw = q[1] * q[3],
+ yz = q[1] * q[2],
+ xw = q[0] * q[3];
+ out[0] = 1 - 2 * (yy + zz);
+ out[1] = 2 * (xy + zw);
+ out[2] = 2 * (zx - yw);
+ out[4] = 2 * (xy - zw);
+ out[5] = 1 - 2 * (zz + xx);
+ out[6] = 2 * (yz + xw);
+ out[8] = 2 * (zx + yw);
+ out[9] = 2 * (yz - xw);
+ out[10] = 1 - 2 * (yy + xx);
+ return out
+};
+
+AnimationBone.prototype.mulSlimDX = function (out, l, r) {
+ var left = {
+ M11: l[0],
+ M12: l[1],
+ M13: l[2],
+ M14: l[3],
+ M21: l[4],
+ M22: l[5],
+ M23: l[6],
+ M24: l[7],
+ M31: l[8],
+ M32: l[9],
+ M33: l[10],
+ M34: l[11],
+ M41: l[12],
+ M42: l[13],
+ M43: l[14],
+ M44: l[15]
+ };
+ var right = {
+ M11: r[0],
+ M12: r[1],
+ M13: r[2],
+ M14: r[3],
+ M21: r[4],
+ M22: r[5],
+ M23: r[6],
+ M24: r[7],
+ M31: r[8],
+ M32: r[9],
+ M33: r[10],
+ M34: r[11],
+ M41: r[12],
+ M42: r[13],
+ M43: r[14],
+ M44: r[15]
+ };
+ out[0] = left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41;
+ out[1] = left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32 + left.M14 * right.M42;
+ out[2] = left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33 + left.M14 * right.M43;
+ out[3] = left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34 + left.M14 * right.M44;
+ out[4] = left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31 + left.M24 * right.M41;
+ out[5] = left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32 + left.M24 * right.M42;
+ out[6] = left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33 + left.M24 * right.M43;
+ out[7] = left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34 + left.M24 * right.M44;
+ out[8] = left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31 + left.M34 * right.M41;
+ out[9] = left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32 + left.M34 * right.M42;
+ out[10] = left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + left.M34 * right.M43;
+ out[11] = left.M31 * right.M14 + left.M32 * right.M24 + left.M33 * right.M34 + left.M34 * right.M44;
+ out[12] = left.M41 * right.M11 + left.M42 * right.M21 + left.M43 * right.M31 + left.M44 * right.M41;
+ out[13] = left.M41 * right.M12 + left.M42 * right.M22 + left.M43 * right.M32 + left.M44 * right.M42;
+ out[14] = left.M41 * right.M13 + left.M42 * right.M23 + left.M43 * right.M33 + left.M44 * right.M43;
+ out[15] = left.M41 * right.M14 + left.M42 * right.M24 + left.M43 * right.M34 + left.M44 * right.M44;
+ return out
+};
+
+export default AnimationBone;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/BaseAnimations.js b/ShadowEditor.Core/src/lol/BaseAnimations.js
new file mode 100644
index 00000000..79025214
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/BaseAnimations.js
@@ -0,0 +1,24 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+var BaseAnimations = {
+ 19: {
+ 0: {
+ all: "idle"
+ }
+ },
+ 32: {
+ 4: {
+ all: "idle1_bow",
+ idle1_bow: "idle1"
+ }
+ },
+ 55: {
+ 7: {
+ idle1_candycane_below: "idle1"
+ }
+ }
+};
+
+export default BaseAnimations;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Bone.js b/ShadowEditor.Core/src/lol/Bone.js
new file mode 100644
index 00000000..e0f2d7e6
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Bone.js
@@ -0,0 +1,28 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function Bone(model, index, r) {
+ var self = this,
+ i;
+ self.model = model;
+ self.index = index;
+ self.name = r.getString().toLowerCase();
+ self.parent = r.getInt32();
+ self.scale = r.getFloat();
+ self.origMatrix = mat4.create();
+ for (i = 0; i < 16; ++i) self.origMatrix[i] = r.getFloat();
+ self.baseMatrix = mat4.clone(self.origMatrix);
+ mat4.transpose(self.baseMatrix, self.baseMatrix);
+ mat4.invert(self.baseMatrix, self.baseMatrix);
+ mat4.transpose(self.origMatrix, self.origMatrix);
+ self.incrMatrix = mat4.create();
+ if (model.version >= 2) {
+ for (i = 0; i < 16; ++i) self.incrMatrix[i] = r.getFloat();
+ mat4.transpose(self.incrMatrix, self.incrMatrix)
+ } else {
+ mat4.identity(self.incrMatrix)
+ }
+};
+
+export default Bone;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Constant.js b/ShadowEditor.Core/src/lol/Constant.js
new file mode 100644
index 00000000..950d23b5
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Constant.js
@@ -0,0 +1,7 @@
+var champions_all = [{ "id": 1, "name": "Annie", "title": "黑暗之女", "chinese": "安妮" }, { "id": 2, "name": "Olaf", "title": "狂战士", "chinese": "奥拉夫" }, { "id": 3, "name": "Galio", "title": "哨兵之殇", "chinese": "加里奥" }, { "id": 4, "name": "Twisted Fate", "title": "卡牌大师", "chinese": "崔斯特" }, { "id": 5, "name": "Xin Zhao", "title": "德邦总管", "chinese": "赵信" }, { "id": 6, "name": "Urgot", "title": "首领之傲", "chinese": "厄加特" }, { "id": 7, "name": "LeBlanc", "title": "诡术妖姬", "chinese": "乐芙兰" }, { "id": 8, "name": "Vladimir", "title": "猩红收割者", "chinese": "弗拉基米尔" }, { "id": 9, "name": "Fiddlesticks", "title": "末日使者", "chinese": "费德提克" }, { "id": 10, "name": "Kayle", "title": "审判天使", "chinese": "凯尔" }, { "id": 11, "name": "Master Yi", "title": "无极剑圣", "chinese": "易大师" }, { "id": 12, "name": "Alistar", "title": "牛头酋长", "chinese": "阿利斯塔" }, { "id": 13, "name": "Ryze", "title": "流浪法师", "chinese": "瑞兹" }, { "id": 14, "name": "Sion", "title": "亡灵勇士", "chinese": "赛恩" }, { "id": 15, "name": "Sivir", "title": "战争女神", "chinese": "希维尔" }, { "id": 16, "name": "Soraka", "title": "众星之子", "chinese": "索拉卡" }, { "id": 17, "name": "Teemo", "title": "迅捷斥候", "chinese": "提莫" }, { "id": 18, "name": "Tristana", "title": "麦林炮手", "chinese": "崔丝塔娜" }, { "id": 19, "name": "Warwick", "title": "嗜血猎手", "chinese": "沃里克" }, { "id": 20, "name": "Nunu", "title": "雪人骑士", "chinese": "努努" }, { "id": 21, "name": "Miss Fortune", "title": "赏金猎人", "chinese": "厄运小姐" }, { "id": 22, "name": "Ashe", "title": "寒冰射手", "chinese": "艾希" }, { "id": 23, "name": "Tryndamere", "title": "蛮族之王", "chinese": "泰达米尔" }, { "id": 24, "name": "Jax", "title": "武器大师", "chinese": "贾克斯" }, { "id": 25, "name": "Morgana", "title": "堕落天使", "chinese": "莫甘娜" }, { "id": 26, "name": "Zilean", "title": "时光守护者", "chinese": "基兰" }, { "id": 27, "name": "Singed", "title": "炼金术士", "chinese": "辛吉德" }, { "id": 28, "name": "Evelynn", "title": "寡妇制造者", "chinese": "伊芙琳" }, { "id": 29, "name": "Twitch", "title": "瘟疫之源", "chinese": "图奇" }, { "id": 30, "name": "Karthus", "title": "死亡颂唱者", "chinese": "卡尔萨斯" }, { "id": 31, "name": "Cho'Gath", "title": "虚空恐惧", "chinese": "科加斯" }, { "id": 32, "name": "Amumu", "title": "殇之木乃伊", "chinese": "阿木木" }, { "id": 33, "name": "Rammus", "title": "披甲龙龟", "chinese": "拉莫斯" }, { "id": 34, "name": "Anivia", "title": "冰晶凤凰", "chinese": "艾尼维亚" }, { "id": 35, "name": "Shaco", "title": "恶魔小丑", "chinese": "萨科" }, { "id": 36, "name": "Dr. Mundo", "title": "祖安狂人", "chinese": "蒙多医生" }, { "id": 37, "name": "Sona", "title": "琴瑟仙女", "chinese": "娑娜" }, { "id": 38, "name": "Kassadin", "title": "虚空行者", "chinese": "卡萨丁" }, { "id": 39, "name": "Irelia", "title": "刀锋意志", "chinese": "艾瑞莉娅" }, { "id": 40, "name": "Janna", "title": "风暴之怒", "chinese": "迦娜" }, { "id": 41, "name": "Gangplank", "title": "海洋之灾", "chinese": "普朗克" }, { "id": 42, "name": "Corki", "title": "英勇投弹手", "chinese": "库奇" }, { "id": 43, "name": "Karma", "title": "天启者", "chinese": "卡尔玛" }, { "id": 44, "name": "Taric", "title": "宝石骑士", "chinese": "塔里克" }, { "id": 45, "name": "Veigar", "title": "邪恶小法师", "chinese": "维迦" }, { "id": 48, "name": "Trundle", "title": "巨魔之王", "chinese": "特朗德尔" }, { "id": 50, "name": "Swain", "title": "策士统领", "chinese": "斯维因" }, { "id": 51, "name": "Caitlyn", "title": "皮城女警", "chinese": "凯特琳" }, { "id": 53, "name": "Blitzcrank", "title": "蒸汽机器人", "chinese": "布里茨" }, { "id": 54, "name": "Malphite", "title": "熔岩巨兽", "chinese": "墨菲特" }, { "id": 55, "name": "Katarina", "title": "不祥之刃", "chinese": "卡特琳娜" }, { "id": 56, "name": "Nocturne", "title": "永恒梦魇", "chinese": "魔腾" }, { "id": 57, "name": "Maokai", "title": "扭曲树精", "chinese": "茂凯" }, { "id": 58, "name": "Renekton", "title": "荒漠屠夫", "chinese": "雷克顿" }, { "id": 59, "name": "Jarvan IV", "title": "德玛西亚皇子", "chinese": "嘉文四世" }, { "id": 60, "name": "Elise", "title": "蜘蛛女皇", "chinese": "伊莉丝" }, { "id": 61, "name": "Orianna", "title": "发条魔灵", "chinese": "奥莉安娜" }, { "id": 62, "name": "Wukong", "title": "齐天大圣", "chinese": "孙悟空" }, { "id": 63, "name": "Brand", "title": "复仇焰魂", "chinese": "布兰德" }, { "id": 64, "name": "Lee Sin", "title": "盲僧", "chinese": "李青" }, { "id": 67, "name": "Vayne", "title": "暗夜猎手", "chinese": "薇恩" }, { "id": 68, "name": "Rumble", "title": "机械公敌", "chinese": "兰博" }, { "id": 69, "name": "Cassiopeia", "title": "魔蛇之拥", "chinese": "卡西奥佩娅" }, { "id": 72, "name": "Skarner", "title": "水晶先锋", "chinese": "斯卡纳" }, { "id": 74, "name": "Heimerdinger", "title": "大发明家", "chinese": "黑默丁格" }, { "id": 75, "name": "Nasus", "title": "沙漠死神", "chinese": "内瑟斯" }, { "id": 76, "name": "Nidalee", "title": "狂野女猎手", "chinese": "奈德丽" }, { "id": 77, "name": "Udyr", "title": "兽灵行者", "chinese": "乌迪尔" }, { "id": 78, "name": "Poppy", "title": "钢铁大使", "chinese": "波比" }, { "id": 79, "name": "Gragas", "title": "酒桶", "chinese": "古拉加斯" }, { "id": 80, "name": "Pantheon", "title": "战争之王", "chinese": "潘森" }, { "id": 81, "name": "Ezreal", "title": "探险家", "chinese": "伊泽瑞尔" }, { "id": 82, "name": "Mordekaiser", "title": "金属大师", "chinese": "莫德凯撒" }, { "id": 83, "name": "Yorick", "title": "掘墓者", "chinese": "约里克" }, { "id": 84, "name": "Akali", "title": "暗影之拳", "chinese": "阿卡丽" }, { "id": 85, "name": "Kennen", "title": "狂暴之心", "chinese": "凯南" }, { "id": 86, "name": "Garen", "title": "德玛西亚之力", "chinese": "盖伦" }, { "id": 89, "name": "Leona", "title": "曙光女神", "chinese": "蕾欧娜" }, { "id": 90, "name": "Malzahar", "title": "虚空先知", "chinese": "玛尔扎哈" }, { "id": 91, "name": "Talon", "title": "刀锋之影", "chinese": "泰隆" }, { "id": 92, "name": "Riven", "title": "放逐之刃", "chinese": "锐雯" }, { "id": 96, "name": "Kog'Maw", "title": "深渊巨口", "chinese": "克格莫" }, { "id": 98, "name": "Shen", "title": "暮光之眼", "chinese": "慎" }, { "id": 99, "name": "Lux", "title": "光辉女郎", "chinese": "拉克丝" }, { "id": 101, "name": "Xerath", "title": "远古巫灵", "chinese": "泽拉斯" }, { "id": 102, "name": "Shyvana", "title": "龙血武姬", "chinese": "希瓦娜" }, { "id": 103, "name": "Ahri", "title": "九尾妖狐", "chinese": "阿狸" }, { "id": 104, "name": "Graves", "title": "法外狂徒", "chinese": "格雷福斯" }, { "id": 105, "name": "Fizz", "title": "潮汐海灵", "chinese": "菲兹" }, { "id": 106, "name": "Volibear", "title": "雷霆咆哮", "chinese": "沃利贝尔" }, { "id": 107, "name": "Rengar", "title": "傲之追猎者", "chinese": "雷恩加尔" }, { "id": 110, "name": "Varus", "title": "惩戒之箭", "chinese": "韦鲁斯" }, { "id": 111, "name": "Nautilus", "title": "深海泰坦", "chinese": "诺提勒斯" }, { "id": 112, "name": "Viktor", "title": "机械先驱", "chinese": "维克托" }, { "id": 113, "name": "Sejuani", "title": "凛冬之怒", "chinese": "瑟庄妮" }, { "id": 114, "name": "Fiora", "title": "无双剑姬", "chinese": "菲奥娜" }, { "id": 115, "name": "Ziggs", "title": "爆破鬼才", "chinese": "吉格斯" }, { "id": 117, "name": "Lulu", "title": "仙灵女巫", "chinese": "璐璐" }, { "id": 119, "name": "Draven", "title": "荣耀行刑官", "chinese": "德莱文" }, { "id": 120, "name": "Hecarim", "title": "战争之影", "chinese": "赫卡里姆" }, { "id": 121, "name": "Kha'Zix", "title": "虚空掠夺者", "chinese": "卡吉斯" }, { "id": 122, "name": "Darius", "title": "诺克萨斯之手", "chinese": "德莱厄斯" }, { "id": 126, "name": "Jayce", "title": "未来守护者", "chinese": "杰斯" }, { "id": 127, "name": "Lissandra", "title": "冰霜女巫", "chinese": "丽桑卓" }, { "id": 131, "name": "Diana", "title": "皎月女神", "chinese": "黛安娜" }, { "id": 133, "name": "Quinn", "title": "德玛西亚之翼", "chinese": "奎因" }, { "id": 134, "name": "Syndra", "title": "暗黑元首", "chinese": "辛德拉" }, { "id": 136, "name": "Aurelion Sol", "title": "铸星龙王", "chinese": "奥瑞利安·索尔" }, { "id": 143, "name": "Zyra", "title": "荆棘之兴", "chinese": "婕拉" }, { "id": 150, "name": "Gnar", "title": "迷失之牙", "chinese": "纳尔" }, { "id": 154, "name": "Zac", "title": "生化魔人", "chinese": "扎克" }, { "id": 157, "name": "Yasuo", "title": "疾风剑豪", "chinese": "亚索" }, { "id": 161, "name": "Vel'Koz", "title": "虚空之眼", "chinese": "维克兹" }, { "id": 163, "name": "Taliyah", "title": "岩雀", "chinese": "塔莉垭" }, { "id": 164, "name": "Camille", "title": "钢铁之影", "chinese": "卡蜜儿" }, { "id": 201, "name": "Braum", "title": "弗雷尔卓德之心", "chinese": "布隆" }, { "id": 202, "name": "Jhin", "title": "戏命师", "chinese": "烬" }, { "id": 203, "name": "Kindred", "title": "永猎双子", "chinese": "千珏" }, { "id": 222, "name": "Jinx", "title": "暴走萝莉", "chinese": "金克丝" }, { "id": 223, "name": "Tahm Kench", "title": "河流之王", "chinese": "塔姆·肯奇" }, { "id": 236, "name": "Lucian", "title": "圣枪游侠", "chinese": "卢锡安" }, { "id": 238, "name": "Zed", "title": "影流之主", "chinese": "劫" }, { "id": 240, "name": "Kled", "title": "暴怒骑士", "chinese": "克烈" }, { "id": 245, "name": "Ekko", "title": "时间刺客", "chinese": "艾克" }, { "id": 254, "name": "Vi", "title": "皮城执法官", "chinese": "蔚" }, { "id": 266, "name": "Aatrox", "title": "暗裔剑魔", "chinese": "亚托克斯" }, { "id": 267, "name": "Nami", "title": "唤潮鲛姬", "chinese": "娜美" }, { "id": 268, "name": "Azir", "title": "沙漠皇帝", "chinese": "阿兹尔" }, { "id": 412, "name": "Thresh", "title": "魂锁典狱长", "chinese": "锤石" }, { "id": 420, "name": "Illaoi", "title": "海兽祭司", "chinese": "俄洛伊" }, { "id": 421, "name": "Rek'Sai", "title": "虚空遁地兽", "chinese": "雷克塞" }, { "id": 427, "name": "Ivern", "title": "翠神", "chinese": "艾翁" }, { "id": 429, "name": "Kalista", "title": "复仇之矛", "chinese": "卡莉丝塔" }, { "id": 432, "name": "Bard", "title": "星界游神", "chinese": "巴德" }, { "id": 497, "name": "Rakan", "title": "幻翎", "chinese": "洛" }, { "id": 498, "name": "Xayah", "title": "逆羽", "chinese": "霞" }, { "id": "sightward", "name": "sightward", "title": "守卫", "chinese": "默认守卫" }, { "id": "sru_dragon_air", "name": "Air Dragon", "title": "风龙", "chinese": "空气龙" }, { "id": "sru_baron", "name": "Baron", "title": "纳什男爵", "chinese": "纳什男爵" }, { "id": "sru_dragon", "name": "Dragon", "title": "龙", "chinese": "龙" }, { "id": "sru_dragon_earth", "name": "Earth Dragon", "title": "土龙", "chinese": "土龙" }, { "id": "sru_dragon_elder", "name": "Elder Dragon", "title": "大龙", "chinese": "大龙" }, { "id": "sru_dragon_fire", "name": "Fire Dragon", "title": "火龙", "chinese": "火龙" }, { "id": "sru_murkwolf", "name": "Murk Wolf", "title": "黑暗狼", "chinese": "黑暗狼" }, { "id": "sru_gromp", "name": "Gromp", "title": "蛤蟆", "chinese": "蛤蟆" }, { "id": "bw_ironback", "name": "Ironback", "title": "铁背", "chinese": "铁背" }, { "id": "kingporo", "name": "King Poro", "title": "魄罗王", "chinese": "魄罗王" }, { "id": "kingporo_porofollower", "name": "King Poro Follower", "title": "魄罗王随从", "chinese": "魄罗王随从" }, { "id": "bw_ocklepod", "name": "Ocklepod", "title": "Ocklepod", "chinese": "Ocklepod" }, { "id": "bw_plundercrab", "name": "Plundercrab", "title": "掠夺蟹", "chinese": "掠夺蟹" }, { "id": "sru_red", "name": "Red Brambleback", "title": "红树", "chinese": "红树" }, { "id": "sru_crab", "name": "Rift Scuttler", "title": "迅捷蟹", "chinese": "迅捷蟹" }, { "id": "tt_spiderboss", "name": "Vilemaw", "title": "蜘蛛王", "chinese": "蜘蛛王" }, { "id": "sru_dragon_water", "name": "Water Dragon", "title": "水龙", "chinese": "水龙" }];
+
+var champions = [{ "id": 1, "name": "Annie", "title": "黑暗之女", "chinese": "安妮" }, { "id": 11, "name": "Master Yi", "title": "无极剑圣", "chinese": "易大师" }, { "id": 22, "name": "Ashe", "title": "寒冰射手", "chinese": "艾希" }, { "id": 40, "name": "Janna", "title": "风暴之怒", "chinese": "迦娜" }, { "id": 86, "name": "Garen", "title": "德玛西亚之力", "chinese": "盖伦" }, { "id": 99, "name": "Lux", "title": "光辉女郎", "chinese": "拉克丝" }, { "id": 103, "name": "Ahri", "title": "九尾妖狐", "chinese": "阿狸" }, { "id": 117, "name": "Lulu", "title": "仙灵女巫", "chinese": "璐璐" }, { "id": 222, "name": "Jinx", "title": "暴走萝莉", "chinese": "金克丝" }, { "id": 498, "name": "Xayah", "title": "逆羽", "chinese": "霞" }, { "id": "bw_plundercrab", "name": "Plundercrab", "title": "掠夺蟹", "chinese": "掠夺蟹" }, { "id": "sru_crab", "name": "Rift Scuttler", "title": "迅捷蟹", "chinese": "迅捷蟹" }];
+
+var animNames = [{ "id": 0, "name": "attack1", "chinese": "攻击1" }, { "id": 1, "name": "attack2", "chinese": "攻击2" }, { "id": 2, "name": "channel", "chinese": "施法" }, { "id": 3, "name": "channel_wndup", "chinese": "施法打断" }, { "id": 4, "name": "crit", "chinese": "重击" }, { "id": 5, "name": "dance", "chinese": "跳舞" }, { "id": 6, "name": "death", "chinese": "死亡" }, { "id": 7, "name": "idle1", "chinese": "站立1" }, { "id": 8, "name": "joke", "chinese": "玩笑" }, { "id": 9, "name": "laugh", "chinese": "大笑" }, { "id": 10, "name": "recall", "chinese": "回城" }, { "id": 11, "name": "run", "chinese": "跑步" }, { "id": 12, "name": "spell1", "chinese": "技能1" }, { "id": 13, "name": "spell2", "chinese": "技能2" }, { "id": 14, "name": "spell3", "chinese": "技能3" }, { "id": 15, "name": "spell4", "chinese": "技能4" }, { "id": 16, "name": "taunt", "chinese": "嘲讽" }, { "id": 17, "name": "attack3", "chinese": "攻击3" }, { "id": 18, "name": "idle2", "chinese": "站立2" }, { "id": 19, "name": "idle3", "chinese": "站立3" }, { "id": 20, "name": "idle4", "chinese": "站立4" }];
+
+export { champions_all, champions, animNames };
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/DataView2.js b/ShadowEditor.Core/src/lol/DataView2.js
new file mode 100644
index 00000000..6d5071bd
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/DataView2.js
@@ -0,0 +1,107 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function DataView2(buffer) {
+ this.buffer = new DataView(buffer);
+ this.position = 0
+};
+
+DataView2.prototype.getBool = function () {
+ var v = this.buffer.getUint8(this.position) != 0;
+ this.position += 1;
+ return v
+};
+
+DataView2.prototype.getUint8 = function () {
+ var v = this.buffer.getUint8(this.position);
+ this.position += 1;
+ return v
+};
+
+DataView2.prototype.getInt8 = function () {
+ var v = this.buffer.getInt8(this.position);
+ this.position += 1;
+ return v
+};
+
+DataView2.prototype.getUint16 = function () {
+ var v = this.buffer.getUint16(this.position, true);
+ this.position += 2;
+ return v
+};
+
+DataView2.prototype.getInt16 = function () {
+ var v = this.buffer.getInt16(this.position, true);
+ this.position += 2;
+ return v
+};
+
+DataView2.prototype.getUint32 = function () {
+ var v = this.buffer.getUint32(this.position, true);
+ this.position += 4;
+ return v
+};
+
+DataView2.prototype.getInt32 = function () {
+ var v = this.buffer.getInt32(this.position, true);
+ this.position += 4;
+ return v
+};
+
+DataView2.prototype.getFloat = function () {
+ var v = this.buffer.getFloat32(this.position, true);
+ this.position += 4;
+ return v
+};
+
+DataView2.prototype.getString = function (len) {
+ if (len === undefined) len = this.getUint16();
+ var str = "";
+ for (var i = 0; i < len; ++i) {
+ str += String.fromCharCode(this.getUint8())
+ }
+ return str
+};
+
+DataView2.prototype.setBool = function (v) {
+ this.buffer.setUint8(this.position, v ? 1 : 0);
+ this.position += 1
+};
+
+DataView2.prototype.setUint8 = function (v) {
+ this.buffer.setUint8(this.position, v);
+ this.position += 1
+};
+
+DataView2.prototype.setInt8 = function (v) {
+ this.buffer.setInt8(this.position, v);
+ this.position += 1
+};
+
+DataView2.prototype.setUint16 = function (v) {
+ this.buffer.setUint16(this.position, v, true);
+ this.position += 2
+};
+
+DataView2.prototype.setInt16 = function (v) {
+ this.buffer.setInt16(this.position, v, true);
+ this.position += 2
+};
+
+DataView2.prototype.setUint32 = function (v) {
+ this.buffer.setUint32(this.position, v, true);
+ this.position += 4
+};
+
+DataView2.prototype.setInt32 = function (v) {
+ this.buffer.setInt32(this.position, v, true);
+ this.position += 4
+};
+
+DataView2.prototype.setFloat = function (v) {
+ this.buffer.setFloat32(this.position, v, true);
+ this.position += 4
+};
+
+export default DataView2;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/HiddenBones.js b/ShadowEditor.Core/src/lol/HiddenBones.js
new file mode 100644
index 00000000..b735b7bb
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/HiddenBones.js
@@ -0,0 +1,921 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+var HiddenBones = {
+ 12: {
+ 9: {
+ recall: {},
+ all: {
+ recall_chair: true
+ }
+ },
+ 10: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 11: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 12: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 13: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 14: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 15: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 16: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 17: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ },
+ 18: {
+ recall: {
+ cowbell: true,
+ stick: true
+ },
+ dancein: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ danceloop: {
+ cata_root: true,
+ catb_root: true,
+ catc_root: true,
+ cork: true,
+ bowl: true,
+ bowl_milk: true,
+ milk_root: true,
+ bottle: true
+ },
+ all: {}
+ }
+ },
+ 21: {
+ 9: {
+ all: {
+ orange: true
+ },
+ recall: {
+ l_weapon: true,
+ r_weapon: true
+ }
+ },
+ 10: {
+ recall: {},
+ all: {
+ tv_joint: true,
+ tv_rabit_ears_joints: true
+ }
+ },
+ 11: {
+ recall: {},
+ all: {
+ tv_joint: true,
+ tv_rabit_ears_joints: true
+ }
+ },
+ 12: {
+ recall: {},
+ all: {
+ tv_joint: true,
+ tv_rabit_ears_joints: true
+ }
+ },
+ 13: {
+ recall: {},
+ all: {
+ tv_joint: true,
+ tv_rabit_ears_joints: true
+ }
+ },
+ 14: {
+ recall: {},
+ all: {
+ tv_joint: true,
+ tv_rabit_ears_joints: true
+ }
+ }
+ },
+ 22: {
+ 8: {
+ all: {
+ c_drone_base: true
+ },
+ joke: {},
+ dance: {}
+ }
+ },
+ 36: {
+ 9: {
+ all: {
+ recall_chair: true
+ },
+ recall: {}
+ }
+ },
+ 41: {
+ 0: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 1: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 2: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 3: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 4: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 5: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 6: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ },
+ 7: {
+ all: {
+ orange1: true,
+ orange2: true,
+ orange3: true
+ },
+ joke: {}
+ }
+ },
+ 44: {
+ 4: {
+ all: {
+ jacket: true
+ },
+ dance: {
+ jacket: true,
+ weapon: true
+ },
+ recall: {
+ weapon: true
+ }
+ }
+ },
+ 55: {
+ 7: {
+ recall: {},
+ all: {
+ xmas_pole_skin07: true
+ }
+ }
+ },
+ 61: {
+ 7: {
+ recall: {},
+ all: {
+ planet1: true,
+ planet2: true,
+ planet3: true,
+ planet4: true,
+ planet5: true,
+ planet6: true
+ }
+ }
+ },
+ 69: {
+ 4: {
+ all: {
+ l_fan: true,
+ r_fan: true
+ },
+ recall: {}
+ }
+ },
+ 80: {
+ 8: {
+ all: {
+ oven: true
+ },
+ recall: {}
+ }
+ },
+ 83: {
+ 0: {
+ all: {},
+ idle2: {
+ weapon: true
+ }
+ },
+ 1: {
+ all: {},
+ idle2: {
+ weapon: true
+ }
+ },
+ 2: {
+ all: {},
+ idle2: {
+ weapon: true
+ }
+ }
+ },
+ 103: {
+ 7: {
+ recall: {},
+ all: {
+ arcade: true
+ }
+ }
+ },
+ 114: {
+ 5: {
+ all: {
+ weapon_krab: true,
+ root_krab: true
+ },
+ recall: {}
+ }
+ },
+ 115: {
+ 4: {
+ all: {
+ sled: true
+ },
+ satcheljump: {
+ bomb: true,
+ bomb_b: true
+ }
+ }
+ },
+ 119: {
+ 4: {
+ all: {
+ chair_root: true,
+ sun_reflector_root: true
+ },
+ recall: {}
+ }
+ },
+ 136: {
+ 0: {
+ all: {
+ shades_sunglass: true
+ },
+ joke: {}
+ },
+ 1: {
+ all: {
+ shades_sunglass: true
+ },
+ joke: {}
+ }
+ },
+ 143: {
+ 4: {
+ attack1: {
+ r_wing: true,
+ l_wing: true
+ },
+ attack2: {
+ r_wing: true,
+ l_wing: true
+ },
+ dance: {
+ r_wing: true,
+ l_wing: true
+ },
+ idle1: {
+ r_wing: true,
+ l_wing: true
+ },
+ idle3: {
+ r_wing: true,
+ l_wing: true
+ },
+ idle4: {
+ r_wing: true,
+ l_wing: true
+ },
+ laugh: {
+ r_wing: true,
+ l_wing: true
+ },
+ run: {
+ r_wing: true,
+ l_wing: true
+ },
+ spell2: {
+ r_wing: true,
+ l_wing: true
+ },
+ all: {}
+ }
+ },
+ 157: {
+ 4: {
+ all: {
+ flute: true
+ },
+ dance: {}
+ },
+ 5: {
+ all: {
+ flute: true
+ },
+ dance: {}
+ },
+ 6: {
+ all: {
+ flute: true
+ },
+ dance: {}
+ },
+ 7: {
+ all: {
+ flute: true
+ },
+ dance: {}
+ },
+ 8: {
+ all: {
+ flute: true
+ },
+ dance: {}
+ }
+ },
+ 201: {
+ 3: {
+ all: {
+ poro: true
+ }
+ }
+ },
+ 222: {
+ 4: {
+ all: {
+ rocket_launcher: true
+ },
+ r_attack1: {},
+ r_attack2: {},
+ r_idle1: {},
+ r_idle_in: {},
+ r_run: {},
+ r_run_fast: {},
+ r_run_haste: {},
+ r_spell2: {},
+ r_spell3: {},
+ r_spell3_run: {},
+ r_spell4: {},
+ respawn_trans_rlauncher: {},
+ rlauncher_spell3: {},
+ spell1a: {}
+ }
+ },
+ 238: {
+ 10: {
+ all: {
+ chair_skin10: true,
+ step1_skin10: true,
+ step2_skin10: true
+ },
+ recall: {}
+ }
+ },
+ 245: {
+ 0: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 1: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 2: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 3: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 4: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 5: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 6: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 7: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 8: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 9: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ },
+ 10: {
+ deathrespawn: {},
+ all: {
+ book_pen: true
+ }
+ }
+ },
+ 254: {
+ 0: {
+ all: {
+ teacup: true
+ },
+ taunt2: {}
+ },
+ 1: {
+ all: {
+ teacup: true
+ },
+ taunt2: {}
+ },
+ 3: {
+ all: {
+ teacup: true
+ },
+ taunt2: {}
+ },
+ 4: {
+ all: {
+ teacup: true
+ },
+ taunt2: {}
+ }
+ },
+ 412: {
+ 1: {
+ all: {
+ coin1: true,
+ coin2: true,
+ coin3: true,
+ coin4: true,
+ coin5: true,
+ coin6: true,
+ coin7: true,
+ treasure_chest: true,
+ treasure_chest_cover: true,
+ tire: true
+ },
+ recall: {
+ tire: true
+ },
+ undersea_recall_loop: {
+ tire: true
+ },
+ undersea_recall_loop2: {
+ coin1: true,
+ coin2: true,
+ coin3: true,
+ coin4: true,
+ coin5: true,
+ coin6: true,
+ coin7: true,
+ treasure_chest: true,
+ treasure_chest_cover: true
+ },
+ undersea_recall_windup: {
+ tire: true
+ },
+ undersea_recall_windup2: {
+ coin1: true,
+ coin2: true,
+ coin3: true,
+ coin4: true,
+ coin5: true,
+ coin6: true,
+ coin7: true,
+ treasure_chest: true,
+ treasure_chest_cover: true
+ }
+ },
+ 5: {
+ all: {
+ mini_root: true
+ },
+ joke: {}
+ }
+ },
+ 420: {
+ 0: {
+ all: {
+ c_tentacle1: true
+ }
+ },
+ 1: {
+ all: {
+ c_tentacle1: true
+ }
+ }
+ },
+ 429: {
+ 3: {
+ death: {
+ altar_spear: true,
+ buffbone_cstm_back_spear1: true,
+ buffbone_cstm_back_spear2: true,
+ buffbone_cstm_back_spear3: true
+ }
+ }
+ },
+ 432: {
+ 0: {
+ all: {
+ follower_root: true
+ },
+ dance: {}
+ },
+ 2: {
+ all: {
+ follower_root: true
+ },
+ dance: {}
+ },
+ 3: {
+ all: {
+ follower_root: true
+ },
+ dance: {}
+ },
+ 4: {
+ all: {
+ follower_root: true
+ },
+ dance: {}
+ }
+ },
+ gnarbig: {
+ 0: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 1: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 2: {
+ all: {
+ rock: true,
+ cane_bot: true,
+ cane_top: true
+ },
+ spell1: {
+ cane_bot: true,
+ cane_top: true
+ },
+ laugh: {
+ cane_bot: true,
+ cane_top: true
+ },
+ recall: {
+ rock: true
+ }
+ },
+ 3: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 4: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 5: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 6: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 7: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 8: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 9: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 10: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 11: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ },
+ 12: {
+ all: {
+ rock: true
+ },
+ spell1: {},
+ laugh: {}
+ }
+ }
+};
+
+export default HiddenBones;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Model.js b/ShadowEditor.Core/src/lol/Model.js
new file mode 100644
index 00000000..a7f74738
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Model.js
@@ -0,0 +1,417 @@
+import {
+ dispatch
+} from '../third_party';
+import DataView from './DataView2';
+import Vertex from './Vertex';
+import Texture from './Texture';
+import Bone from './Bone';
+import HiddenBones from './HiddenBones';
+import Animation from './Animation';
+import BaseAnimations from './BaseAnimations';
+import AnimationBone from './AnimationBone';
+
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function Model(options) {
+ var self = this;
+ self.champion = options.champion || "1";
+ self.skin = options.skin || 0;
+ self.meshUrl = options.meshUrl;
+ self.animUrl = options.animUrl;
+ self.textureUrl = options.textureUrl;
+
+ self.loaded = false;
+ self.animsLoaded = false;
+
+ self.meshes = null;
+ self.vertices = null;
+ self.indices = null;
+ self.transforms = null;
+ self.bones = null;
+ self.boneLookup = {};
+ self.animIndex = -1;
+ self.animName = null;
+ self.baseAnim = null;
+ self.newAnimation = false;
+ self.animTime = 0;
+ self.tmpMat = mat4.create();
+ self.tmpVec = vec4.create();
+ self.ANIMATED = true;
+
+ self.dispatch = dispatch('load', 'loadMesh', 'loadTexture', 'loadAnim');
+
+ self.hiddenBones = null;
+ var hiddenBones = HiddenBones;
+ if (hiddenBones[self.champion] !== undefined) {
+ if (hiddenBones[self.champion][self.skin] !== undefined) {
+ self.hiddenBones = hiddenBones[self.champion][self.skin]
+ }
+ }
+
+ self.ambientColor = [.35, .35, .35, 1];
+ self.primaryColor = [1, 1, 1, 1];
+ self.secondaryColor = [.35, .35, .35, 1];
+ self.lightDir1 = vec3.create();
+ self.lightDir2 = vec3.create();
+ self.lightDir3 = vec3.create();
+ vec3.normalize(self.lightDir1, [5, 5, -5]);
+ vec3.normalize(self.lightDir2, [5, 5, 5]);
+ vec3.normalize(self.lightDir3, [-5, -5, -5]);
+
+ self.texture = null;
+ self.geometry = new THREE.BufferGeometry();
+ self.material = new THREE.MeshPhongMaterial();
+
+ var promise1 = new Promise(resolve => {
+ self.dispatch.on('loadMesh.Model', () => {
+ resolve();
+ });
+ });
+ var promise2 = new Promise(resolve => {
+ self.dispatch.on('loadTexture.Model', () => {
+ resolve();
+ });
+ });
+ var promise3 = new Promise(resolve => {
+ self.dispatch.on('loadAnim.Model', () => {
+ resolve();
+ });
+ });
+ Promise.all([promise1, promise2, promise3]).then(() => {
+ self.dispatch.call('load');
+ });
+};
+
+Model.prototype.getAnimations = function () {
+ if (!this.animations) {
+ return null;
+ }
+ var names = [];
+ this.animations.forEach(function (n) {
+ names.push(n.name);
+ });
+ return names;
+};
+
+Model.prototype.getAnimation = function (name) {
+ var self = this,
+ i, animIndex = -1;
+ if (!self.animations) {
+ return animIndex
+ };
+ name = name.toLowerCase();
+ if (name == "idle" || name == "attack") {
+ var anims = [],
+ re = new RegExp(name + "[0-9]*");
+ for (i = 0; i < self.animations.length; ++i) {
+ if (self.animations[i].name.search(re) == 0) anims.push(i)
+ }
+ if (anims.length > 0) {
+ animIndex = anims[0];
+ }
+ } else {
+ for (i = 0; i < self.animations.length; ++i) {
+ if (self.animations[i].name == name) {
+ animIndex = i;
+ break
+ }
+ }
+ }
+ return animIndex
+};
+
+Model.prototype.setAnimation = function (name) {
+ var self = this;
+ self.animName = name;
+ self.newAnimation = true;
+};
+
+Model.prototype.update = function (time) {
+ var self = this,
+ i, j;
+
+ if (self.animTime == 0) {
+ self.animTime = time;
+ }
+
+ if (!self.loaded || !self.vertices || !self.animations || self.animations.length == 0) {
+ return;
+ }
+
+ self.animIndex = self.getAnimation(self.animName);
+ if (self.animIndex == -1) {
+ self.animIndex = 0;
+ self.animName = "idle";
+ }
+ var baseAnims = BaseAnimations;
+ if (baseAnims[self.champion] !== undefined) {
+ if (baseAnims[self.champion][self.skin] !== undefined) {
+ var baseAnim = baseAnims[self.champion][self.skin],
+ baseIndex = -1;
+
+ if (baseAnim[self.animations[self.animIndex].name]) {
+ baseIndex = self.getAnimation(baseAnim[self.animations[self.animIndex].name]);
+ } else if (baseAnim["all"]) {
+ baseIndex = self.getAnimation(baseAnim["all"]);
+ }
+
+ if (baseIndex > -1) {
+ self.baseAnim = self.animations[baseIndex];
+ } else {
+ self.baseAnim = null;
+ }
+ }
+ }
+
+ var deltaTime = time - self.animTime;
+ var anim = self.animations[self.animIndex];
+
+ if (deltaTime >= anim.duration) {
+ self.animTime = time;
+ deltaTime = 0
+ }
+
+ if (self.ANIMATED) {
+ var timePerFrame = 1e3 / anim.fps;
+ var frame = Math.floor(deltaTime / timePerFrame);
+ var r = deltaTime % timePerFrame / timePerFrame;
+ var hiddenBones = {};
+ if (self.hiddenBones) {
+ if (self.hiddenBones[anim.name]) {
+ hiddenBones = self.hiddenBones[anim.name];
+ } else if (self.hiddenBones["all"]) {
+ hiddenBones = self.hiddenBones["all"];
+ }
+ }
+ var b;
+ if (self.version >= 1) {
+ for (i = 0; i < self.bones.length; ++i) {
+ b = self.bones[i];
+ if (hiddenBones[b.name]) {
+ mat4.identity(self.tmpMat);
+ mat4.scale(self.tmpMat, self.tmpMat, vec3.set(self.tmpVec, 0, 0, 0));
+ mat4.copy(self.transforms[i], self.tmpMat)
+ } else if (anim.lookup[b.name] !== undefined) {
+ anim.bones[anim.lookup[b.name]].update(i, frame, r)
+ } else if (self.baseAnim && self.baseAnim.lookup[b.name] !== undefined) {
+ self.baseAnim.bones[self.baseAnim.lookup[b.name]].update(i, frame, r)
+ } else {
+ if (b.parent != -1) {
+ AnimationBone.prototype.mulSlimDX(self.transforms[i], b.incrMatrix, self.transforms[b.parent])
+ } else {
+ mat4.copy(self.transforms[i], b.incrMatrix)
+ }
+ }
+ }
+ } else {
+ for (i = 0; i < anim.bones.length; ++i) {
+ b = anim.bones[i];
+ if (self.boneLookup[b.bone] !== undefined) {
+ b.update(self.boneLookup[b.bone], frame, r)
+ } else {
+ var parentBone = anim.bones[i - 1];
+ if (!parentBone) continue;
+ if (parentBone.index + 1 < self.transforms.length) {
+ mat4.copy(self.transforms[parentBone.index + 1], self.transforms[parentBone.index])
+ }
+ b.index = parentBone.index + 1
+ }
+ }
+ }
+ var numBones = Math.min(self.transforms.length, self.bones.length);
+ for (i = 0; i < numBones; ++i) {
+ AnimationBone.prototype.mulSlimDX(self.transforms[i], self.bones[i].baseMatrix, self.transforms[i])
+ }
+ mat4.identity(self.tmpMat);
+ var numVerts = self.vertices.length,
+ vec = self.tmpVec,
+ position = self.geometry.attributes.position.array,
+ normal = self.geometry.attributes.normal.array,
+ v, w, m, idx;
+ for (i = 0; i < numVerts; ++i) {
+ v = self.vertices[i];
+ idx = i * 3;
+ position[idx] = position[idx + 1] = position[idx + 2] = 0;
+ normal[idx] = normal[idx + 1] = normal[idx + 2] = 0;
+ for (j = 0; j < 4; ++j) {
+ if (v.weights[j] > 0) {
+ w = v.weights[j];
+ m = anim.fps == 1 ? self.tmpMat : self.transforms[v.bones[j]];
+ vec3.transformMat4(vec, v.position, m);
+ position[idx] += vec[0] * w;
+ position[idx + 1] += vec[1] * w;
+ position[idx + 2] += vec[2] * w;
+ vec4.transformMat4(vec, v.normal, m);
+ normal[idx] += vec[0] * w;
+ normal[idx + 1] += vec[1] * w;
+ normal[idx + 2] += vec[2] * w
+ }
+ }
+ }
+ self.geometry.attributes.position.needsUpdate = true;
+ self.geometry.attributes.normal.needsUpdate = true;
+ }
+ if (self.newAnimation) {
+ self.newAnimation = false
+ }
+};
+
+Model.prototype.load = function () {
+ var self = this;
+ var loader = new THREE.FileLoader();
+ loader.setResponseType('arraybuffer');
+ loader.load(self.meshUrl, function (buffer) {
+ self.loadMesh(buffer);
+ });
+};
+
+Model.prototype.loadMesh = function (buffer) {
+ if (!buffer) {
+ console.error("Bad buffer for DataView");
+ return
+ }
+ var self = this,
+ r = new DataView(buffer),
+ i,
+ v,
+ idx;
+ try {
+ var magic = r.getUint32();
+ if (magic != 604210091) {
+ console.log("Bad magic value");
+ return
+ }
+ } catch (err) {
+ alert("Model currently isn't loading! We're sorry and hope to have this fixed soon.");
+ console.log(err);
+ return
+ }
+ self.version = r.getUint32();
+ var animFile = r.getString();
+ var textureFile = r.getString();
+ if (animFile && animFile.length > 0) {
+ var loader = new THREE.FileLoader();
+ loader.setResponseType('arraybuffer');
+ loader.load(self.animUrl, function (buffer) {
+ self.loadAnim(buffer)
+ self.dispatch.call('loadAnim');
+ });
+ }
+ if (textureFile && textureFile.length > 0) {
+ self.texture = new Texture(self, self.textureUrl)
+ }
+ var numMeshes = r.getUint32();
+ if (numMeshes > 0) {
+ self.meshes = new Array(numMeshes);
+ for (i = 0; i < numMeshes; ++i) {
+ var name = r.getString().toLowerCase();
+ var vStart = r.getUint32();
+ var vCount = r.getUint32();
+ var iStart = r.getUint32();
+ var iCount = r.getUint32();
+ self.meshes[i] = {
+ name: name,
+ vStart: vStart,
+ vCount: vCount,
+ iStart: iStart,
+ iCount: iCount
+ }
+ }
+ }
+ var numVerts = r.getUint32();
+ if (numVerts > 0) {
+ self.vertices = new Array(numVerts);
+ self.vbData = new Float32Array(numVerts * 8);
+ var position = [];
+ var normal = [];
+ var uv = [];
+ for (i = 0; i < numVerts; ++i) {
+ idx = i * 8;
+ self.vertices[i] = v = new Vertex(r);
+ self.vbData[idx] = v.position[0];
+ self.vbData[idx + 1] = v.position[1];
+ self.vbData[idx + 2] = v.position[2];
+ self.vbData[idx + 3] = v.normal[0];
+ self.vbData[idx + 4] = v.normal[1];
+ self.vbData[idx + 5] = v.normal[2];
+ self.vbData[idx + 6] = v.u;
+ self.vbData[idx + 7] = v.v
+
+ position.push(v.position[0], v.position[1], v.position[2]);
+ normal.push(v.normal[0], v.normal[1], v.normal[2]);
+ uv.push(v.u, v.v);
+ }
+ self.geometry.addAttribute('position',
+ new THREE.BufferAttribute(new Float32Array(position), 3));
+ self.geometry.addAttribute('normal',
+ new THREE.BufferAttribute(new Float32Array(normal), 3));
+ self.geometry.addAttribute('uv',
+ new THREE.BufferAttribute(new Float32Array(uv), 2));
+ }
+ var numIndices = r.getUint32();
+ if (numIndices > 0) {
+ self.indices = new Array(numIndices);
+ for (i = 0; i < numIndices; ++i) {
+ self.indices[i] = r.getUint16()
+ }
+ self.geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(self.indices), 1));
+ }
+ var numBones = r.getUint32();
+ if (numBones > 0) {
+ self.transforms = new Array(numBones);
+ self.bones = new Array(numBones);
+ for (i = 0; i < numBones; ++i) {
+ self.bones[i] = new Bone(self, i, r);
+ if (self.boneLookup[self.bones[i].name] !== undefined) {
+ self.bones[i].name = self.bones[i].name + "2"
+ }
+ self.boneLookup[self.bones[i].name] = i;
+ self.transforms[i] = new mat4.create
+ }
+ }
+ self.loaded = true;
+ self.dispatch.call('loadMesh');
+};
+
+Model.prototype.loadAnim = function (buffer) {
+ if (!buffer) {
+ console.error("Bad buffer for DataView");
+ return
+ }
+ var self = this,
+ r = new DataView(buffer),
+ i;
+ var magic = r.getUint32();
+ if (magic != 604210092) {
+ console.log("Bad magic value");
+ return
+ }
+ var version = r.getUint32();
+ if (version >= 2) {
+ var compressedData = new Uint8Array(buffer, r.position);
+ var data = null;
+ try {
+ data = pako.inflate(compressedData)
+ } catch (err) {
+ console.log("Decompression error: " + err);
+ return
+ }
+ r = new DataView(data.buffer)
+ }
+ var numAnims = r.getUint32();
+ if (numAnims > 0) {
+ self.animations = new Array(numAnims);
+ for (i = 0; i < numAnims; ++i) {
+ self.animations[i] = new Animation(self, r, version)
+ }
+ }
+ self.animsLoaded = true
+};
+
+Model.prototype.on = function (eventName, callback) {
+ this.dispatch.on(eventName, callback);
+};
+
+export default Model;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Texture.js b/ShadowEditor.Core/src/lol/Texture.js
new file mode 100644
index 00000000..0dd40800
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Texture.js
@@ -0,0 +1,30 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function Texture(model, url) {
+ var self = this;
+ self.model = model;
+ self.url = url;
+ self.texture = null;
+ self.load()
+};
+
+Texture.prototype.load = function () {
+ var self = this;
+
+ self.texture = new THREE.TextureLoader().load(self.url, function (texture) {
+ self.onLoad.call(self, texture);
+ });
+};
+
+Texture.prototype.onLoad = function (texture) {
+ var self = this;
+ texture.flipY = false;
+ self.model.material.map = texture;
+ self.model.material.needsUpdate = true;
+
+ self.model.dispatch.call('loadTexture');
+};
+
+export default Texture;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/lol/Vertex.js b/ShadowEditor.Core/src/lol/Vertex.js
new file mode 100644
index 00000000..dc58e591
--- /dev/null
+++ b/ShadowEditor.Core/src/lol/Vertex.js
@@ -0,0 +1,22 @@
+/**
+ * @author lolking / http://www.lolking.net/models
+ * @author tengge / https://github.com/tengge1
+ */
+function Vertex(r) {
+ var self = this,
+ i;
+ self.position = [r.getFloat(), r.getFloat(), r.getFloat()];
+ self.normal = [r.getFloat(), r.getFloat(), r.getFloat(), 0];
+ self.u = r.getFloat();
+ self.v = r.getFloat();
+ self.bones = new Array(4);
+ for (i = 0; i < 4; ++i) {
+ self.bones[i] = r.getUint8();
+ }
+ self.weights = new Array(4);
+ for (i = 0; i < 4; ++i) {
+ self.weights[i] = r.getFloat();
+ }
+};
+
+export default Vertex;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/Fire.js b/ShadowEditor.Core/src/object/component/Fire.js
new file mode 100644
index 00000000..4792ddbe
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/Fire.js
@@ -0,0 +1,42 @@
+/**
+ * 火焰
+ */
+function Fire(camera, options = {}) {
+ THREE.Object3D.call(this);
+
+ VolumetricFire.texturePath = 'assets/textures/VolumetricFire/';
+
+ var width = options.width || 2;
+ var height = options.height || 4;
+ var depth = options.depth || 2;
+ var sliceSpacing = options.sliceSpacing || 0.5;
+
+ var fire = new VolumetricFire(
+ width,
+ height,
+ depth,
+ sliceSpacing,
+ camera
+ );
+
+ this.add(fire.mesh);
+
+ fire.mesh.name = '火焰';
+
+ this.name = '火焰';
+ this.position.y = 2;
+
+ Object.assign(this.userData, {
+ type: 'Fire',
+ fire: fire,
+ width: width,
+ height: height,
+ depth: depth,
+ sliceSpacing: sliceSpacing
+ });
+}
+
+Fire.prototype = Object.create(THREE.Object3D.prototype);
+Fire.prototype.constructor = Fire;
+
+export default Fire;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/ParticleEmitter.js b/ShadowEditor.Core/src/object/component/ParticleEmitter.js
new file mode 100644
index 00000000..3cdb3c95
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/ParticleEmitter.js
@@ -0,0 +1,62 @@
+/**
+ * 粒子发射器
+ */
+function ParticleEmitter(group, emitter) {
+ THREE.Object3D.call(this);
+
+ group = group || new SPE.Group({
+ texture: {
+ value: new THREE.TextureLoader().load('assets/textures/SPE/smokeparticle.png')
+ },
+ maxParticleCount: 2000
+ });
+
+ emitter = emitter || new SPE.Emitter({
+ maxAge: {
+ value: 2
+ },
+ position: {
+ value: new THREE.Vector3(0, 0, 0),
+ spread: new THREE.Vector3(0, 0, 0)
+ },
+
+ acceleration: {
+ value: new THREE.Vector3(0, -10, 0),
+ spread: new THREE.Vector3(10, 0, 10)
+ },
+
+ velocity: {
+ value: new THREE.Vector3(0, 25, 0),
+ spread: new THREE.Vector3(10, 7.5, 10)
+ },
+
+ color: {
+ value: [new THREE.Color('white'), new THREE.Color('red')]
+ },
+
+ size: {
+ value: 1
+ },
+
+ particleCount: 2000
+ });
+
+ group.addEmitter(emitter);
+
+ group.mesh.name = '粒子';
+
+ this.add(group.mesh);
+
+ this.name = '粒子发射器';
+
+ Object.assign(this.userData, {
+ type: 'ParticleEmitter',
+ group: group,
+ emitter: emitter
+ });
+}
+
+ParticleEmitter.prototype = Object.create(THREE.Object3D.prototype);
+ParticleEmitter.prototype.constructor = ParticleEmitter;
+
+export default ParticleEmitter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/Sky.js b/ShadowEditor.Core/src/object/component/Sky.js
new file mode 100644
index 00000000..fc3dad4a
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/Sky.js
@@ -0,0 +1,60 @@
+/**
+ * 天空
+ * @param {*} options 选项
+ */
+function Sky(options) {
+ THREE.Object3D.call(this);
+
+ options = options || {};
+
+ var turbidity = options.turbidity || 10; // 浑浊度
+ var rayleigh = options.rayleigh || 2; // 瑞利
+ var luminance = options.luminance || 1; // 亮度
+ var mieCoefficient = options.mieCoefficient || 0.005;
+ var mieDirectionalG = options.mieDirectionalG || 0.8;
+
+ var distance = 400000;
+
+ var sky = new THREE.Sky();
+ sky.scale.setScalar(450000);
+
+ this.add(sky);
+
+ var sunSphere = new THREE.Mesh(
+ new THREE.SphereBufferGeometry(20000, 16, 8),
+ new THREE.MeshBasicMaterial({ color: 0xffffff })
+ );
+
+ sunSphere.position.y = -700000;
+ sunSphere.visible = false;
+
+ this.add(sunSphere);
+
+ var uniforms = sky.material.uniforms;
+ uniforms.turbidity.value = turbidity;
+ uniforms.rayleigh.value = rayleigh;
+ uniforms.luminance.value = luminance;
+ uniforms.mieCoefficient.value = mieCoefficient;
+ uniforms.mieDirectionalG.value = mieDirectionalG;
+ var theta = Math.PI * (0.49 - 0.5);
+ var phi = 2 * Math.PI * (0.25 - 0.5);
+ sunSphere.position.x = distance * Math.cos(phi);
+ sunSphere.position.y = distance * Math.sin(phi) * Math.sin(theta);
+ sunSphere.position.z = distance * Math.sin(phi) * Math.cos(theta);
+ sunSphere.visible = true;
+ uniforms.sunPosition.value.copy(sunSphere.position);
+
+ this.userData = {
+ type: 'Sky',
+ turbidity: turbidity,
+ rayleigh: rayleigh,
+ luminance: luminance,
+ mieCoefficient: mieCoefficient,
+ mieDirectionalG: mieDirectionalG
+ };
+}
+
+Sky.prototype = Object.create(THREE.Object3D.prototype);
+Sky.prototype.constructor = Sky;
+
+export default Sky;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/Smoke.js b/ShadowEditor.Core/src/object/component/Smoke.js
new file mode 100644
index 00000000..3385aed2
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/Smoke.js
@@ -0,0 +1,78 @@
+import vertexShader from './shader/smoke_vertex.glsl';
+import fragmentShader from './shader/smoke_fragment.glsl';
+
+/**
+ * 烟
+ * @author yomotsu / http://yomotsu.net
+ * ported from http://webgl-fire.appspot.com/html/fire.html
+ *
+ * https://www.youtube.com/watch?v=jKRHmQmduDI
+ * https://graphics.ethz.ch/teaching/former/imagesynthesis_06/miniprojects/p3/
+ * https://www.iusb.edu/math-compsci/_prior-thesis/YVanzine_thesis.pdf
+ * @param {*} camera 相机
+ * @param {*} renderer 渲染器
+ * @param {*} options 选项
+ */
+function Smoke(camera, renderer, options = {}) {
+ var particleCount = options.particleCount || 32;
+ var size = options.size || 3;
+ var lifetime = options.lifetime || 10;
+
+ // 几何体
+ var geometry = new THREE.BufferGeometry();
+
+ var position = new Float32Array(particleCount * 3);
+ var shift = new Float32Array(particleCount);
+
+ for (var i = 0; i < particleCount; i++) {
+ position[i * 3 + 0] = THREE.Math.randFloat(-0.5, 0.5);
+ position[i * 3 + 1] = 2.4;
+ position[i * 3 + 3] = THREE.Math.randFloat(-0.5, 0.5);
+ shift[i] = Math.random() * 1;
+ }
+
+ geometry.addAttribute('position', new THREE.BufferAttribute(position, 3));
+ geometry.addAttribute('shift', new THREE.BufferAttribute(shift, 1));
+
+ // 材质
+ var texture = (new THREE.TextureLoader()).load('assets/textures/VolumetricFire/smoke.png');
+
+ var uniforms = {
+ time: { type: 'f', value: 0 },
+ size: { type: 'f', value: size },
+ texture: { type: 't', value: texture },
+ lifetime: { type: 'f', value: lifetime },
+ projection: { type: 'f', value: Math.abs(renderer.domElement.height / (2 * Math.tan(THREE.Math.degToRad(camera.fov)))) }
+ };
+
+ var material = new THREE.ShaderMaterial({
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ uniforms: uniforms,
+ blending: THREE.AdditiveBlending,
+ transparent: true,
+ depthWrite: false
+ });
+
+ THREE.Points.call(this, geometry, material);
+
+ this.sortParticles = true;
+
+ this.name = '烟';
+
+ Object.assign(this.userData, {
+ type: 'Smoke',
+ particleCount: particleCount,
+ size: size,
+ lifetime: lifetime
+ });
+}
+
+Smoke.prototype = Object.create(THREE.Points.prototype);
+Smoke.prototype.constructor = Smoke;
+
+Smoke.prototype.update = function (elapsed) {
+ this.material.uniforms.time.value = elapsed;
+};
+
+export default Smoke;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/shader/smoke_fragment.glsl b/ShadowEditor.Core/src/object/component/shader/smoke_fragment.glsl
new file mode 100644
index 00000000..d83a5d24
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/shader/smoke_fragment.glsl
@@ -0,0 +1,9 @@
+uniform sampler2D texture;
+varying float progress;
+
+void main() {
+
+ vec3 color = vec3( 1. );
+ gl_FragColor = texture2D( texture, gl_PointCoord ) * vec4( color, .3 * ( 1. - progress ) );
+
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/component/shader/smoke_vertex.glsl b/ShadowEditor.Core/src/object/component/shader/smoke_vertex.glsl
new file mode 100644
index 00000000..3dcdb64c
--- /dev/null
+++ b/ShadowEditor.Core/src/object/component/shader/smoke_vertex.glsl
@@ -0,0 +1,23 @@
+attribute float shift;
+uniform float time;
+uniform float size;
+uniform float lifetime;
+uniform float projection;
+varying float progress;
+
+float cubicOut( float t ) {
+
+ float f = t - 1.0;
+ return f * f * f + 1.0;
+
+}
+
+void main () {
+
+ progress = fract( time * 2. / lifetime + shift );
+ float eased = cubicOut( progress );
+ vec3 pos = vec3( position.x * eased, position.y * eased, position.z );
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1. );
+ gl_PointSize = ( projection * size ) / gl_Position.w;
+
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Box.js b/ShadowEditor.Core/src/object/geometry/Box.js
new file mode 100644
index 00000000..03195b92
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Box.js
@@ -0,0 +1,17 @@
+/**
+ * 正方体
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Box(geometry = new THREE.BoxBufferGeometry(1, 1, 1), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '正方体';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Box.prototype = Object.create(THREE.Mesh.prototype);
+Box.prototype.constructor = Box;
+
+export default Box;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Circle.js b/ShadowEditor.Core/src/object/geometry/Circle.js
new file mode 100644
index 00000000..4951855a
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Circle.js
@@ -0,0 +1,17 @@
+/**
+ * 圆
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Circle(geometry = new THREE.CircleBufferGeometry(1, 32), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '圆';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Circle.prototype = Object.create(THREE.Mesh.prototype);
+Circle.prototype.constructor = Circle;
+
+export default Circle;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Cylinder.js b/ShadowEditor.Core/src/object/geometry/Cylinder.js
new file mode 100644
index 00000000..9f666840
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Cylinder.js
@@ -0,0 +1,17 @@
+/**
+ * 圆柱体
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Cylinder(geometry = new THREE.CylinderBufferGeometry(1, 1, 2, 32, 1, false), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '圆柱体';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Cylinder.prototype = Object.create(THREE.Mesh.prototype);
+Cylinder.prototype.constructor = Cylinder;
+
+export default Cylinder;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Group.js b/ShadowEditor.Core/src/object/geometry/Group.js
new file mode 100644
index 00000000..0ac03f6e
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Group.js
@@ -0,0 +1,12 @@
+/**
+ * 组
+ */
+function Group() {
+ THREE.Object3D.call(this);
+ this.name = '组';
+}
+
+Group.prototype = Object.create(THREE.Object3D.prototype);
+Group.prototype.constructor = Group;
+
+export default Group;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Icosahedron.js b/ShadowEditor.Core/src/object/geometry/Icosahedron.js
new file mode 100644
index 00000000..1fe7e7b6
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Icosahedron.js
@@ -0,0 +1,17 @@
+/**
+ * 二十面体
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Icosahedron(geometry = new THREE.IcosahedronBufferGeometry(1, 2), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '二十面体';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Icosahedron.prototype = Object.create(THREE.Mesh.prototype);
+Icosahedron.prototype.constructor = Icosahedron;
+
+export default Icosahedron;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Lathe.js b/ShadowEditor.Core/src/object/geometry/Lathe.js
new file mode 100644
index 00000000..b060f07d
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Lathe.js
@@ -0,0 +1,31 @@
+var points = [
+ new THREE.Vector2(0, 0),
+ new THREE.Vector2(4, 0),
+ new THREE.Vector2(3.5, 0.5),
+ new THREE.Vector2(1, 0.75),
+ new THREE.Vector2(0.8, 1),
+ new THREE.Vector2(0.8, 4),
+ new THREE.Vector2(1, 4.2),
+ new THREE.Vector2(1.4, 4.8),
+ new THREE.Vector2(2, 5),
+ new THREE.Vector2(2.5, 5.4),
+ new THREE.Vector2(3, 12)
+];
+
+/**
+ * 酒杯
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Lathe(geometry = new THREE.LatheBufferGeometry(points, 20, 0, 2 * Math.PI), material = new THREE.MeshStandardMaterial({ side: THREE.DoubleSide })) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '酒杯';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Lathe.prototype = Object.create(THREE.Mesh.prototype);
+Lathe.prototype.constructor = Lathe;
+
+export default Lathe;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Plane.js b/ShadowEditor.Core/src/object/geometry/Plane.js
new file mode 100644
index 00000000..7892ade9
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Plane.js
@@ -0,0 +1,18 @@
+/**
+ * 平板
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Plane(geometry = new THREE.PlaneBufferGeometry(50, 50), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '平板';
+ this.rotation.x = -Math.PI / 2;
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Plane.prototype = Object.create(THREE.Mesh.prototype);
+Plane.prototype.constructor = Plane;
+
+export default Plane;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Sphere.js b/ShadowEditor.Core/src/object/geometry/Sphere.js
new file mode 100644
index 00000000..21c51721
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Sphere.js
@@ -0,0 +1,17 @@
+/**
+ * 球体
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Sphere(geometry = new THREE.SphereBufferGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '球体';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Sphere.prototype = Object.create(THREE.Mesh.prototype);
+Sphere.prototype.constructor = Sphere;
+
+export default Sphere;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Sprite.js b/ShadowEditor.Core/src/object/geometry/Sprite.js
new file mode 100644
index 00000000..43aec864
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Sprite.js
@@ -0,0 +1,14 @@
+/**
+ * 精灵
+ * @param {*} material 材质
+ */
+function Sprite(material = new THREE.SpriteMaterial()) {
+ THREE.Sprite.call(this, material);
+
+ this.name = '精灵';
+}
+
+Sprite.prototype = Object.create(THREE.Sprite.prototype);
+Sprite.prototype.constructor = Sprite;
+
+export default Sprite;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Teapot.js b/ShadowEditor.Core/src/object/geometry/Teapot.js
new file mode 100644
index 00000000..eaf17f06
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Teapot.js
@@ -0,0 +1,31 @@
+/**
+ * 茶壶
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Teapot(geometry = new THREE.TeapotBufferGeometry(3, 10, true, true, true, true, true), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ // 修改TeapotBufferGeometry类型错误问题,原来是BufferGeometry
+ geometry.type = 'TeapotBufferGeometry';
+
+ // 修复TeapotBufferGeometry缺少parameters参数问题
+ geometry.parameters = {
+ size: 3,
+ segments: 10,
+ bottom: true,
+ lid: true,
+ body: true,
+ fitLid: true,
+ blinn: true
+ };
+
+ this.name = '茶壶';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Teapot.prototype = Object.create(THREE.Mesh.prototype);
+Teapot.prototype.constructor = Teapot;
+
+export default Teapot;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Text.js b/ShadowEditor.Core/src/object/geometry/Text.js
new file mode 100644
index 00000000..28973a8e
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Text.js
@@ -0,0 +1,43 @@
+import StringUtils from '../../utils/StringUtils';
+
+/**
+ * 文本
+ * @param {*} text 文字
+ */
+function Text(text = '文字') {
+ var canvas = document.createElement('canvas');
+
+ var fontSize = 64;
+
+ var ctx = canvas.getContext('2d');
+ ctx.font = `${fontSize}px sans-serif`;
+
+ var textMetrics = ctx.measureText(text);
+ canvas.width = StringUtils.makePowOfTwo(textMetrics.width);
+ canvas.height = fontSize;
+ ctx.textBaseline = 'hanging';
+ ctx.font = `${fontSize}px sans-serif`; // 重新设置画布大小,前面设置的ctx属性全部失效
+
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = 'rgba(255,255,255,1)';
+ ctx.fillText(text, (canvas.width - textMetrics.width) / 2, 0);
+
+ var map = new THREE.CanvasTexture(canvas);
+
+ var geometry = new THREE.PlaneBufferGeometry(canvas.width / 10, canvas.height / 10);
+ var material = new THREE.MeshBasicMaterial({
+ color: 0xffffff,
+ map: map,
+ transparent: true
+ });
+
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = text;
+}
+
+Text.prototype = Object.create(THREE.Mesh.prototype);
+Text.prototype.constructor = Text;
+
+export default Text;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/Torus.js b/ShadowEditor.Core/src/object/geometry/Torus.js
new file mode 100644
index 00000000..34a7cee4
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/Torus.js
@@ -0,0 +1,17 @@
+/**
+ * 轮胎
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function Torus(geometry = new THREE.TorusBufferGeometry(2, 1, 32, 32, Math.PI * 2), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '轮胎';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+Torus.prototype = Object.create(THREE.Mesh.prototype);
+Torus.prototype.constructor = Torus;
+
+export default Torus;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/geometry/TorusKnot.js b/ShadowEditor.Core/src/object/geometry/TorusKnot.js
new file mode 100644
index 00000000..e4490ea9
--- /dev/null
+++ b/ShadowEditor.Core/src/object/geometry/TorusKnot.js
@@ -0,0 +1,17 @@
+/**
+ * 纽结
+ * @param {*} geometry 几何体
+ * @param {*} material 材质
+ */
+function TorusKnot(geometry = new THREE.TorusKnotBufferGeometry(2, 0.8, 64, 12, 2, 3), material = new THREE.MeshStandardMaterial()) {
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '纽结';
+ this.castShadow = true;
+ this.receiveShadow = true;
+}
+
+TorusKnot.prototype = Object.create(THREE.Mesh.prototype);
+TorusKnot.prototype.constructor = TorusKnot;
+
+export default TorusKnot;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/light/HemisphereLight.js b/ShadowEditor.Core/src/object/light/HemisphereLight.js
new file mode 100644
index 00000000..5ac0eccd
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/HemisphereLight.js
@@ -0,0 +1,38 @@
+import vertexShader from './shader/hemisphere_vertex.glsl';
+import fragmentShader from './shader/hemisphere_fragment.glsl';
+
+/**
+ * 半球光
+ * @param {*} skyColor
+ * @param {*} groundColor
+ * @param {*} intensity
+ */
+function HemisphereLight(skyColor, groundColor, intensity) {
+ THREE.HemisphereLight.call(this, skyColor, groundColor, intensity);
+
+ var uniforms = {
+ topColor: { value: new THREE.Color(skyColor) },
+ bottomColor: { value: new THREE.Color(groundColor) },
+ offset: { value: 33 },
+ exponent: { value: 0.6 }
+ };
+
+ var skyGeo = new THREE.SphereBufferGeometry(4000, 32, 15);
+ var skyMat = new THREE.ShaderMaterial({
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ uniforms: uniforms,
+ side: THREE.BackSide
+ });
+
+ var sky = new THREE.Mesh(skyGeo, skyMat);
+ sky.name = '天空';
+ sky.userData.type = 'sky';
+
+ this.add(sky);
+}
+
+HemisphereLight.prototype = Object.create(THREE.HemisphereLight.prototype);
+HemisphereLight.prototype.constructor = HemisphereLight;
+
+export default HemisphereLight;
diff --git a/ShadowEditor.Core/src/object/light/PhysicalLight.js b/ShadowEditor.Core/src/object/light/PhysicalLight.js
new file mode 100644
index 00000000..67cccc42
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/PhysicalLight.js
@@ -0,0 +1,53 @@
+// ref for lumens: http://www.power-sure.com/lumens.htm
+var bulbLuminousPowers = {
+ "110000 lm (1000W)": 110000,
+ "3500 lm (300W)": 3500,
+ "1700 lm (100W)": 1700,
+ "800 lm (60W)": 800,
+ "400 lm (40W)": 400,
+ "180 lm (25W)": 180,
+ "20 lm (4W)": 20,
+ "Off": 0
+};
+
+// ref for solar irradiances: https://en.wikipedia.org/wiki/Lux
+var hemiLuminousIrradiances = {
+ "0.0001 lx (无月之夜)": 0.0001,
+ "0.002 lx (夜晚辉光)": 0.002,
+ "0.5 lx (满月)": 0.5,
+ "3.4 lx (城市暮光)": 3.4,
+ "50 lx (客厅)": 50,
+ "100 lx (很阴沉)": 100,
+ "350 lx (办公室)": 350,
+ "400 lx (日出日落)": 400,
+ "1000 lx (阴沉)": 1000,
+ "18000 lx (阳光)": 18000,
+ "50000 lx (太阳直射)": 50000
+};
+
+var params = {
+ shadows: true,
+ exposure: 0.68,
+ bulbPower: Object.keys(bulbLuminousPowers)[4],
+ hemiIrradiance: Object.keys(hemiLuminousIrradiances)[0]
+};
+
+/**
+ * 物理光源
+ */
+function PhysicalLight(color, intensity, distance, decay) {
+ THREE.PointLight.call(this, color, intensity, distance, decay);
+
+ var bulbGeometry = new THREE.SphereBufferGeometry(0.02, 16, 8);
+ var bulbMat = new THREE.MeshStandardMaterial({
+ emissive: 0xffffee,
+ emissiveIntensity: 1,
+ color: new THREE.Color(color)
+ });
+ this.add(new THREE.Mesh(bulbGeometry, bulbMat));
+}
+
+PhysicalLight.prototype = Object.create(THREE.PhysicalLight.prototype);
+PhysicalLight.prototype.constructor = PhysicalLight;
+
+export default PhysicalLight;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/light/PointLight.js b/ShadowEditor.Core/src/object/light/PointLight.js
new file mode 100644
index 00000000..3631e173
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/PointLight.js
@@ -0,0 +1,41 @@
+/**
+ * 点光源
+ */
+function PointLight(color, intensity, distance, decay) {
+ THREE.PointLight.call(this, color, intensity, distance, decay);
+
+ var geometry = new THREE.SphereBufferGeometry(0.2, 16, 8);
+ var material = new THREE.MeshBasicMaterial({
+ color: color
+ });
+ var mesh = new THREE.Mesh(geometry, material);
+
+ // 帮助器
+ mesh.name = '帮助器';
+ mesh.userData.type = 'helper';
+
+ this.add(mesh);
+
+ // 光晕
+ var textureLoader = new THREE.TextureLoader();
+ var textureFlare0 = textureLoader.load('assets/textures/lensflare/lensflare0.png');
+ var textureFlare3 = textureLoader.load('assets/textures/lensflare/lensflare3.png');
+
+ // 光晕
+ var lensflare = new THREE.Lensflare();
+ lensflare.addElement(new THREE.LensflareElement(textureFlare0, 40, 0.01, new THREE.Color(color)));
+ lensflare.addElement(new THREE.LensflareElement(textureFlare3, 60, 0.2));
+ lensflare.addElement(new THREE.LensflareElement(textureFlare3, 35, 0.4));
+ lensflare.addElement(new THREE.LensflareElement(textureFlare3, 60, 0.6));
+ lensflare.addElement(new THREE.LensflareElement(textureFlare3, 45, 0.8));
+
+ lensflare.name = '光晕';
+ lensflare.userData.type = 'lensflare';
+
+ this.add(lensflare);
+}
+
+PointLight.prototype = Object.create(THREE.PointLight.prototype);
+PointLight.prototype.constructor = PointLight;
+
+export default PointLight;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/light/RectAreaLight.js b/ShadowEditor.Core/src/object/light/RectAreaLight.js
new file mode 100644
index 00000000..411a1f0a
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/RectAreaLight.js
@@ -0,0 +1,31 @@
+/**
+ * 点光源
+ */
+function RectAreaLight(color, intensity, width, height) {
+ THREE.RectAreaLight.call(this, color, intensity, width, height);
+
+ // 正面
+ var rectLightMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial());
+ rectLightMesh.scale.x = width;
+ rectLightMesh.scale.y = height;
+
+ rectLightMesh.name = '正面';
+ rectLightMesh.userData.type = 'frontSide';
+
+ this.add(rectLightMesh);
+
+ var rectLightMeshBack = new THREE.Mesh(new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial({ color: 0x080808 }));
+ rectLightMeshBack.scale.x = width;
+ rectLightMeshBack.scale.y = height;
+ rectLightMeshBack.rotation.y = Math.PI;
+
+ rectLightMesh.name = '背面';
+ rectLightMesh.userData.type = 'backSide';
+
+ this.add(rectLightMeshBack);
+}
+
+RectAreaLight.prototype = Object.create(THREE.RectAreaLight.prototype);
+RectAreaLight.prototype.constructor = RectAreaLight;
+
+export default RectAreaLight;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/light/shader/hemisphere_fragment.glsl b/ShadowEditor.Core/src/object/light/shader/hemisphere_fragment.glsl
new file mode 100644
index 00000000..fd29e1f9
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/shader/hemisphere_fragment.glsl
@@ -0,0 +1,11 @@
+uniform vec3 topColor;
+uniform vec3 bottomColor;
+uniform float offset;
+uniform float exponent;
+
+varying vec3 vWorldPosition;
+
+void main() {
+ float h = normalize(vWorldPosition + offset).y;
+ gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h , 0.0), exponent), 0.0)), 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/light/shader/hemisphere_vertex.glsl b/ShadowEditor.Core/src/object/light/shader/hemisphere_vertex.glsl
new file mode 100644
index 00000000..7e6e145e
--- /dev/null
+++ b/ShadowEditor.Core/src/object/light/shader/hemisphere_vertex.glsl
@@ -0,0 +1,7 @@
+varying vec3 vWorldPosition;
+
+void main() {
+ vec4 worldPosition = modelMatrix * vec4(position, 1.0);
+ vWorldPosition = worldPosition.xyz;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/terrain/PerlinTerrain.js b/ShadowEditor.Core/src/object/terrain/PerlinTerrain.js
new file mode 100644
index 00000000..42294fe8
--- /dev/null
+++ b/ShadowEditor.Core/src/object/terrain/PerlinTerrain.js
@@ -0,0 +1,111 @@
+/**
+ * 柏林地形
+ * @param {*} width 地形宽度
+ * @param {*} depth 地形深度
+ * @param {*} widthSegments 宽度分段
+ * @param {*} depthSegments 深度分段
+ * @param {*} quality 地形质量
+ */
+function PerlinTerrain(width = 1000, depth = 1000, widthSegments = 256, depthSegments = 256, quality = 80) {
+ // 创建地形几何体
+ var geometry = new THREE.PlaneBufferGeometry(width, depth, widthSegments - 1, depthSegments - 1);
+ geometry.rotateX(-Math.PI / 2);
+
+ var vertices = geometry.attributes.position.array;
+
+ var data = this.generateHeight(widthSegments, depthSegments, quality);
+
+ for (var i = 0, l = vertices.length; i < l; i++) {
+ vertices[i * 3 + 1] = data[i]; // 给顶点数组y分量赋值(地面高度)
+ }
+
+ geometry.computeFaceNormals();
+
+ // 创建光照贴图
+ var texture = new THREE.CanvasTexture(this.generateTexture(data, widthSegments, depthSegments));
+ texture.wrapS = THREE.ClampToEdgeWrapping;
+ texture.wrapT = THREE.ClampToEdgeWrapping;
+
+ // 创建网格
+ THREE.Mesh.call(this, geometry, new THREE.MeshLambertMaterial({ map: texture }));
+
+ this.name = '地形';
+
+ this.position.y = -50;
+
+ Object.assign(this.userData, {
+ type: 'PerlinTerrain',
+ width: width,
+ depth: depth,
+ widthSegments: widthSegments,
+ depthSegments: depthSegments,
+ quality: quality,
+ });
+}
+
+PerlinTerrain.prototype = Object.create(THREE.Mesh.prototype);
+PerlinTerrain.prototype.constructor = PerlinTerrain;
+
+/**
+ * 生成高程数据
+ * @param {*} width 宽度
+ * @param {*} height 高度
+ * @param {*} quality 质量
+ */
+PerlinTerrain.prototype.generateHeight = function (width, height, quality) {
+ var data = new Uint8Array(width * height);
+ var perlin = new ImprovedNoise();
+
+ for (var i = 0; i < width; i++) {
+ for (var j = 0; j < height; j++) {
+ data[i * height + j] = Math.abs(perlin.noise(i / quality, j / quality, 0) * quality);
+ }
+ }
+
+ return data;
+};
+
+/**
+ * 将光照烘培到贴图上
+ * @param {*} data 高程数据
+ * @param {*} width 宽度
+ * @param {*} height 高度
+ */
+PerlinTerrain.prototype.generateTexture = function (data, width, height) {
+ // 创建ImageData
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+
+ var context = canvas.getContext('2d');
+ context.fillStyle = '#000';
+ context.fillRect(0, 0, width, height);
+
+ var image = context.getImageData(0, 0, canvas.width, canvas.height);
+ var imageData = image.data;
+
+ // 计算光照强度
+ var sun = new THREE.Vector3(1, 1, 1);
+ sun.normalize();
+
+ var vector3 = new THREE.Vector3(0, 0, 0);
+ var shade;
+
+ for (var i = 0, j = 0, l = imageData.length; i < l; i += 4, j++) { // i-像素RGBA分量索引,j-高程数据索引
+ vector3.x = data[j - 2] - data[j + 2];
+ vector3.y = 2;
+ vector3.z = data[j - width * 2] - data[j + width * 2];
+ vector3.normalize();
+ shade = vector3.dot(sun);
+ imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
+ imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
+ imageData[i + 2] = (shade * 96) * (0.5 + data[j] * 0.007);
+ }
+
+ // 将光照强度写入canvas
+ context.putImageData(image, 0, 0);
+
+ return canvas;
+};
+
+export default PerlinTerrain;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/terrain/PhysicsTerrain.js b/ShadowEditor.Core/src/object/terrain/PhysicsTerrain.js
new file mode 100644
index 00000000..dbaa50b9
--- /dev/null
+++ b/ShadowEditor.Core/src/object/terrain/PhysicsTerrain.js
@@ -0,0 +1,170 @@
+/**
+ * 物理地形
+ */
+function PhysicsTerrain() {
+ // 灰阶高度参数
+ var terrainWidthExtents = 100; // 地形宽度范围
+ var terrainDepthExtents = 100; // 地形深度范围
+ var terrainWidth = 128; // 地形宽度
+ var terrainDepth = 128; // 地形深度
+ var terrainMinHeight = -2;
+ var terrainMaxHeight = 8;
+
+ // 创建几何体
+ var geometry = new THREE.PlaneBufferGeometry(terrainWidthExtents, terrainDepthExtents, terrainWidth - 1, terrainDepth - 1);
+ geometry.rotateX(-Math.PI / 2);
+
+ var vertices = geometry.attributes.position.array;
+
+ var heightData = this.generateHeight(terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight);
+
+ for (var i = 0, j = 0, l = vertices.length; i < l; i++ , j += 3) {
+ // j + 1 because it is the y component that we modify
+ vertices[j + 1] = heightData[i];
+ }
+
+ geometry.computeVertexNormals();
+
+ // 创建材质
+ var material = new THREE.MeshPhongMaterial({
+ color: 0xC7C7C7
+ });
+
+ // 创建网格
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '地形';
+ this.castShadow = true;
+ this.receiveShadow = true;
+
+ // 下载贴图
+ var loader = new THREE.TextureLoader();
+ loader.load(`assets/textures/grid.png`, texture => {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(terrainWidth - 1, terrainDepth - 1);
+ material.map = texture;
+ material.needsUpdate = true;
+ });
+
+ // 物理
+ var mass = 0;
+ var position = this.position;
+ var quaternion = this.quaternion;
+
+ var transform = new Ammo.btTransform();
+ transform.setIdentity();
+ transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
+ transform.setRotation(new Ammo.btQuaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w));
+ var state = new Ammo.btDefaultMotionState(transform);
+
+ var shape = this.createTerrainShape(terrainWidth, terrainDepth, terrainWidthExtents, terrainDepthExtents, heightData, terrainMinHeight, terrainMaxHeight);
+
+ var localInertia = new Ammo.btVector3(0, 0, 0);
+
+ var body = new Ammo.btRigidBody(new Ammo.btRigidBodyConstructionInfo(mass, state, shape, localInertia));
+
+ this.userData.physicsBody = body;
+}
+
+PhysicsTerrain.prototype = Object.create(THREE.Mesh.prototype);
+PhysicsTerrain.prototype.constructor = PhysicsTerrain;
+
+/**
+ * 生成高程数据(正弦曲线)
+ * @param {*} width
+ * @param {*} depth
+ * @param {*} minHeight
+ * @param {*} maxHeight
+ */
+PhysicsTerrain.prototype.generateHeight = function (width, depth, minHeight, maxHeight) {
+ var size = width * depth;
+ var data = new Float32Array(size);
+ var hRange = maxHeight - minHeight;
+ var w2 = width / 2;
+ var d2 = depth / 2;
+ var phaseMult = 12;
+ var p = 0;
+ for (var j = 0; j < depth; j++) {
+ for (var i = 0; i < width; i++) {
+ var radius = Math.sqrt(Math.pow((i - w2) / w2, 2.0) + Math.pow((j - d2) / d2, 2.0));
+ var height = (Math.sin(radius * phaseMult) + 1) * 0.5 * hRange + minHeight;
+ data[p] = height;
+ p++;
+ }
+ }
+ return data;
+};
+
+/**
+ * 创建物理地形形状
+ * @param {*} terrainWidth
+ * @param {*} terrainDepth
+ * @param {*} terrainWidthExtents
+ * @param {*} terrainDepthExtents
+ * @param {*} heightData
+ * @param {*} terrainMinHeight
+ * @param {*} terrainMaxHeight
+ */
+PhysicsTerrain.prototype.createTerrainShape = function (terrainWidth, terrainDepth, terrainWidthExtents, terrainDepthExtents, heightData, terrainMinHeight, terrainMaxHeight) {
+ // 此参数并未真正使用,因为我们使用的是PHY_FLOAT高度数据类型,因此会被忽略。
+ var heightScale = 1;
+ // 向上轴 0表示X,1表示Y,2表示Z。通常使用1=Y。
+ var upAxis = 1;
+ // hdt,高度数据类型。 使用“PHY_FLOAT”。 可能的值为“PHY_FLOAT”,“PHY_UCHAR”,“PHY_SHORT”。
+ var hdt = "PHY_FLOAT";
+ // 根据您的需要设置(反转三角形)。
+ var flipQuadEdges = false;
+ // 在Ammo堆中创建高度数据缓冲区。
+ var ammoHeightData = Ammo._malloc(4 * terrainWidth * terrainDepth);
+ // 将javascript高度数据数组复制到Ammo。
+ var p = 0;
+ var p2 = 0;
+ var dh = (terrainMinHeight + terrainMaxHeight) / 2;
+ for (var j = 0; j < terrainDepth; j++) {
+ for (var i = 0; i < terrainWidth; i++) {
+ // 将32位浮点数写入内存。
+ Ammo.HEAPF32[ammoHeightData + p2 >> 2] = heightData[p] + dh;
+ p++;
+ // 4个字节/浮点数
+ p2 += 4;
+ }
+ }
+ // 创建高度场物理形状
+ var heightFieldShape = new Ammo.btHeightfieldTerrainShape(
+ terrainWidth,
+ terrainDepth,
+ ammoHeightData,
+ heightScale,
+ terrainMinHeight,
+ terrainMaxHeight,
+ upAxis,
+ hdt,
+ flipQuadEdges
+ );
+ // 设置水平缩放
+ var scaleX = terrainWidthExtents / (terrainWidth - 1);
+ var scaleZ = terrainDepthExtents / (terrainDepth - 1);
+ heightFieldShape.setLocalScaling(new Ammo.btVector3(scaleX, 1, scaleZ));
+ heightFieldShape.setMargin(0.05);
+ return heightFieldShape;
+};
+
+PhysicsTerrain.prototype.update = function (deltaTime, physicsWorld) {
+ physicsWorld.stepSimulation(deltaTime, 10);
+
+ var body = this.userData.physics.body;
+ var state = body.getMotionState();
+
+ if (state) {
+ var transformAux1 = new Ammo.btTransform();
+
+ state.getWorldTransform(transformAux1);
+ var p = transformAux1.getOrigin();
+ var q = transformAux1.getRotation();
+ this.position.set(p.x(), p.y(), p.z());
+ this.quaternion.set(q.x(), q.y(), q.z(), q.w());
+ }
+};
+
+export default PhysicsTerrain;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/terrain/ShaderTerrain.js b/ShadowEditor.Core/src/object/terrain/ShaderTerrain.js
new file mode 100644
index 00000000..52ab566c
--- /dev/null
+++ b/ShadowEditor.Core/src/object/terrain/ShaderTerrain.js
@@ -0,0 +1,179 @@
+import HeightVertexShader from './shader/height_vertex.glsl';
+import HeightFragmentShader from './shader/height_fragment.glsl';
+
+/**
+ * 着色器地形
+ * @param {*} renderer 渲染器
+ * @param {*} options 参数
+ */
+function ShaderTerrain(renderer, options) {
+ var width = options.width || window.innerWidth; // 画布宽度
+ var height = options.height || window.innerHeight; // 画布高度
+
+ // 地形参数
+ var rx = 256, ry = 256, // 分辨率
+ animDelta = 0, // 动画间隔
+ animDeltaDir = -1, // 动画方向
+ lightVal = 0, // 光源强度
+ lightDir = 1; // 光源方向
+
+ // 场景
+ var scene = new THREE.Scene();
+
+ // 相机
+ var camera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, -10000, 10000);
+ camera.position.z = 100;
+ scene.add(camera);
+
+ // 高程贴图
+ var heightMap = new THREE.WebGLRenderTarget(rx, ry, {
+ minFilter: THREE.LinearFilter,
+ magFilter: THREE.LinearFilter,
+ format: THREE.RGBFormat
+ });
+ heightMap.texture.generateMipmaps = false;
+
+ // 法线贴图
+ var normalMap = new THREE.WebGLRenderTarget(rx, ry, {
+ minFilter: THREE.LinearFilter,
+ magFilter: THREE.LinearFilter,
+ format: THREE.RGBFormat
+ });
+ normalMap.texture.generateMipmaps = false;
+
+ // 高光贴图
+ var specularMap = new THREE.WebGLRenderTarget(2048, 2048, {
+ minFilter: THREE.LinearFilter,
+ magFilter: THREE.LinearFilter,
+ format: THREE.RGBFormat
+ });
+ specularMap.texture.generateMipmaps = false;
+
+ // 下载纹理
+ var loadingManager = new THREE.LoadingManager(() => {
+ this.visible = true;
+ });
+
+ var textureLoader = new THREE.TextureLoader(loadingManager);
+
+ var diffuseTexture1 = textureLoader.load("assets/textures/terrain/grasslight-big.jpg"); // 漫反射纹理1
+ var diffuseTexture2 = textureLoader.load("assets/textures/terrain/backgrounddetailed6.jpg"); // 漫反射纹理2
+ var detailTexture = textureLoader.load("assets/textures/terrain/grasslight-big-nm.jpg"); // 细节纹理
+
+ diffuseTexture1.wrapS = diffuseTexture1.wrapT = THREE.RepeatWrapping;
+ diffuseTexture2.wrapS = diffuseTexture2.wrapT = THREE.RepeatWrapping;
+ detailTexture.wrapS = detailTexture.wrapT = THREE.RepeatWrapping;
+
+ // 创建高程材质
+ var heightUniforms = {
+ time: { value: 1.0 },
+ scale: { value: new THREE.Vector2(1.5, 1.5) },
+ offset: { value: new THREE.Vector2(0, 0) }
+ };
+
+ var hightMaterial = this.createShaderMaterial(HeightVertexShader, HeightFragmentShader, heightUniforms, false);
+
+ // 创建法线材质
+ var normalUniforms = THREE.UniformsUtils.clone(THREE.NormalMapShader.uniforms);
+ normalUniforms.height.value = 0.05;
+ normalUniforms.resolution.value.set(rx, ry);
+ normalUniforms.heightMap.value = heightMap.texture;
+
+ var normalMaterial = this.createShaderMaterial(THREE.NormalMapShader.vertexShader, THREE.NormalMapShader.fragmentShader, normalUniforms, false);
+
+ // 创建地形材质
+ var terrainShader = THREE.ShaderTerrain["terrain"];
+
+ var terrainUniforms = THREE.UniformsUtils.clone(terrainShader.uniforms);
+
+ terrainUniforms['tDisplacement'].value = heightMap.texture; // 位移贴图
+ terrainUniforms['uDisplacementScale'].value = 375; // 位移贴图缩放
+
+ terrainUniforms['tNormal'].value = normalMap.texture; // 法线贴图
+ terrainUniforms['uNormalScale'].value = 3.5; // 法线贴图缩放
+
+ terrainUniforms['specular'].value.setHex(0xffffff); // 高光颜色
+ terrainUniforms['diffuse'].value.setHex(0xffffff); // 漫反射颜色
+ terrainUniforms['shininess'].value = 30; // 光泽
+
+ terrainUniforms['tSpecular'].value = specularMap.texture; // 高光贴图
+ terrainUniforms['enableSpecular'].value = true; // 是否启用高光贴图
+
+ terrainUniforms['tDiffuse1'].value = diffuseTexture1; // 漫反射纹理1
+ terrainUniforms['enableDiffuse1'].value = true; // 是否启用漫反射纹理1
+
+ terrainUniforms['tDiffuse2'].value = diffuseTexture2; // 漫反射纹理2
+ terrainUniforms['enableDiffuse2'].value = true; // 是否启用漫反射纹理2
+
+ terrainUniforms['tDetail'].value = detailTexture; // 细节纹理
+ terrainUniforms['uRepeatOverlay'].value.set(6, 6); // 重复叠加次数
+
+ var terrainMaterial = this.createShaderMaterial(terrainShader.vertexShader, terrainShader.fragmentShader, terrainUniforms, true);
+
+ // 贴图生成渲染目标
+ var quadTarget = new THREE.Mesh(new THREE.PlaneBufferGeometry(width, height), new THREE.MeshBasicMaterial({ color: 0x000000 }));
+ quadTarget.position.z = -500;
+ scene.add(quadTarget);
+
+ // 创建网格
+ var geometry = new THREE.PlaneBufferGeometry(6000, 6000, 256, 256);
+ THREE.BufferGeometryUtils.computeTangents(geometry);
+
+ THREE.Mesh.call(this, geometry, terrainMaterial);
+
+ this.name = '地形';
+ this.position.set(0, -30, 0);
+ this.rotation.x = -Math.PI / 2;
+ this.scale.set(0.1, 0.1, 0.1);
+
+ // 动画函数
+ function update(deltaTime) {
+ if (!this.visible) {
+ return;
+ }
+
+ var fLow = 0.1,
+ fHigh = 0.8;
+
+ var lightVal = THREE.Math.clamp(lightVal + 0.5 * deltaTime * lightDir, fLow, fHigh);
+ var valNorm = (lightVal - fLow) / (fHigh - fLow);
+
+ terrainUniforms['uNormalScale'].value = THREE.Math.mapLinear(valNorm, 0, 1, 0.6, 3.5);
+
+ animDelta = THREE.Math.clamp(animDelta + 0.00075 * animDeltaDir, 0, 0.05);
+ heightUniforms['time'].value += deltaTime * animDelta;
+ heightUniforms['offset'].value.x += deltaTime * 0.05;
+
+ // 生成高程贴图
+ quadTarget.material = hightMaterial;
+ renderer.render(scene, camera, heightMap, true);
+
+ // 生成法线贴图
+ quadTarget.material = normalMaterial;
+ renderer.render(scene, camera, normalMap, true);
+ };
+
+ this.update = update.bind(this);
+}
+
+ShaderTerrain.prototype = Object.create(THREE.Mesh.prototype);
+ShaderTerrain.prototype.constructor = ShaderTerrain;
+
+/**
+ * 创建着色器材质
+ * @param {*} vertexShader 顶点着色器
+ * @param {*} fragmentShader 片源着色器
+ * @param {*} uniforms 变量
+ * @param {*} lights 是否使用光源
+ */
+ShaderTerrain.prototype.createShaderMaterial = function (vertexShader, fragmentShader, uniforms, lights) {
+ return new THREE.ShaderMaterial({
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ uniforms: uniforms,
+ lights: lights,
+ fog: true
+ });
+};
+
+export default ShaderTerrain;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/terrain/shader/height_fragment.glsl b/ShadowEditor.Core/src/object/terrain/shader/height_fragment.glsl
new file mode 100644
index 00000000..2759e302
--- /dev/null
+++ b/ShadowEditor.Core/src/object/terrain/shader/height_fragment.glsl
@@ -0,0 +1,87 @@
+//
+// Description : Array and textureless GLSL 3D simplex noise function.
+// Author : Ian McEwan, Ashima Arts.
+// Maintainer : ijm
+// Lastmod : 20110409 (stegu)
+// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
+// Distributed under the MIT License. See LICENSE file.
+//
+uniform float time;
+varying vec2 vUv;
+
+vec4 permute( vec4 x ) {
+ return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 );
+}
+
+vec4 taylorInvSqrt( vec4 r ) {
+ return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+float snoise( vec3 v ) {
+ const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 );
+ const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 );
+ // First corner
+ vec3 i = floor( v + dot( v, C.yyy ) );
+ vec3 x0 = v - i + dot( i, C.xxx );
+ // Other corners
+ vec3 g = step( x0.yzx, x0.xyz );
+ vec3 l = 1.0 - g;
+ vec3 i1 = min( g.xyz, l.zxy );
+ vec3 i2 = max( g.xyz, l.zxy );
+ vec3 x1 = x0 - i1 + 1.0 * C.xxx;
+ vec3 x2 = x0 - i2 + 2.0 * C.xxx;
+ vec3 x3 = x0 - 1. + 3.0 * C.xxx;
+ // Permutations
+ i = mod( i, 289.0 );
+ vec4 p = permute( permute( permute(
+ i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) )
+ + i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) )
+ + i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) );
+ // Gradients
+ // ( N*N points uniformly over a square, mapped onto an octahedron.)
+ float n_ = 1.0 / 7.0; // N=7
+ vec3 ns = n_ * D.wyz - D.xzx;
+ vec4 j = p - 49.0 * floor( p * ns.z *ns.z ); // mod(p,N*N)
+ vec4 x_ = floor( j * ns.z );
+ vec4 y_ = floor( j - 7.0 * x_ ); // mod(j,N)
+ vec4 x = x_ *ns.x + ns.yyyy;
+ vec4 y = y_ *ns.x + ns.yyyy;
+ vec4 h = 1.0 - abs( x ) - abs( y );
+ vec4 b0 = vec4( x.xy, y.xy );
+ vec4 b1 = vec4( x.zw, y.zw );
+ vec4 s0 = floor( b0 ) * 2.0 + 1.0;
+ vec4 s1 = floor( b1 ) * 2.0 + 1.0;
+ vec4 sh = -step( h, vec4( 0.0 ) );
+ vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
+ vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
+ vec3 p0 = vec3( a0.xy, h.x );
+ vec3 p1 = vec3( a0.zw, h.y );
+ vec3 p2 = vec3( a1.xy, h.z );
+ vec3 p3 = vec3( a1.zw, h.w );
+ // Normalise gradients
+ vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) );
+ p0 *= norm.x;
+ p1 *= norm.y;
+ p2 *= norm.z;
+ p3 *= norm.w;
+ // Mix final noise value
+ vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 );
+ m = m * m;
+ return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ),
+ dot( p2, x2 ), dot( p3, x3 ) ) );
+}
+
+float surface3( vec3 coord ) {
+ float n = 0.0;
+ n += 1.0 * abs( snoise( coord ) );
+ n += 0.5 * abs( snoise( coord * 2.0 ) );
+ n += 0.25 * abs( snoise( coord * 4.0 ) );
+ n += 0.125 * abs( snoise( coord * 8.0 ) );
+ return n;
+}
+
+void main( void ) {
+ vec3 coord = vec3( vUv, -time );
+ float n = surface3( coord );
+ gl_FragColor = vec4( vec3( n, n, n ), 1.0 );
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/terrain/shader/height_vertex.glsl b/ShadowEditor.Core/src/object/terrain/shader/height_vertex.glsl
new file mode 100644
index 00000000..d8d13b52
--- /dev/null
+++ b/ShadowEditor.Core/src/object/terrain/shader/height_vertex.glsl
@@ -0,0 +1,8 @@
+varying vec2 vUv;
+uniform vec2 scale;
+uniform vec2 offset;
+
+void main( void ) {
+ vUv = uv * scale + offset;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/water/Water.js b/ShadowEditor.Core/src/object/water/Water.js
new file mode 100644
index 00000000..f46a0f8d
--- /dev/null
+++ b/ShadowEditor.Core/src/object/water/Water.js
@@ -0,0 +1,137 @@
+import HeightmapFragmentShader from './shader/heightmap_fragment.glsl';
+import SmoothFragmentShader from './shader/smooth_fragment.glsl';
+import WaterVertexShader from './shader/water_vertex.glsl';
+
+/**
+ * 水
+ */
+function Water(renderer) {
+ var BOUNDS = 512;
+ var WIDTH = 128;
+ var materialColor = 0x0040C0;
+
+ // 创建几何体
+ var geometry = new THREE.PlaneBufferGeometry(BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1);
+
+ // 创建材质
+ var material = new THREE.ShaderMaterial({
+ uniforms: THREE.UniformsUtils.merge([
+ THREE.ShaderLib['phong'].uniforms, {
+ heightmap: {
+ value: null
+ }
+ }
+ ]),
+ vertexShader: WaterVertexShader,
+ fragmentShader: THREE.ShaderChunk['meshphong_frag']
+ });
+ material.lights = true;
+ // 来自MeshPhongMaterial的属性
+ material.color = new THREE.Color(materialColor);
+ material.specular = new THREE.Color(0x111111);
+ material.shininess = 50;
+ // 根据材质的值设置uniforms
+ material.uniforms.diffuse.value = material.color;
+ material.uniforms.specular.value = material.specular;
+ material.uniforms.shininess.value = Math.max(material.shininess, 1e-4);
+ material.uniforms.opacity.value = material.opacity;
+ // 设置Defines
+ material.defines.WIDTH = WIDTH.toFixed(1);
+ material.defines.BOUNDS = BOUNDS.toFixed(1);
+
+ var waterUniforms = material.uniforms;
+
+ // 创建网格
+ THREE.Mesh.call(this, geometry, material);
+
+ this.name = '液体';
+
+ this.rotation.x = -Math.PI / 2;
+
+ this.matrixAutoUpdate = false;
+ this.updateMatrix();
+
+ var gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
+
+ var heightmap0 = gpuCompute.createTexture();
+ this.fillTexture(heightmap0, WIDTH);
+
+ var heightmapVariable = gpuCompute.addVariable('heightmap', HeightmapFragmentShader, heightmap0);
+ gpuCompute.setVariableDependencies(heightmapVariable, [heightmapVariable]);
+
+ heightmapVariable.material.uniforms.mousePos = {
+ value: new THREE.Vector2(10000, 10000)
+ };
+ heightmapVariable.material.uniforms.mouseSize = {
+ value: 20.0
+ };
+ heightmapVariable.material.uniforms.viscosityConstant = {
+ value: 0
+ };
+ heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed(1);
+
+ var error = gpuCompute.init();
+ if (error !== null) {
+ console.error(error);
+ }
+
+ var smoothShader = gpuCompute.createShaderMaterial(SmoothFragmentShader, {
+ texture: {
+ value: null
+ }
+ });
+
+ this.heightmapVariable = heightmapVariable;
+ this.gpuCompute = gpuCompute;
+ this.waterUniforms = waterUniforms;
+}
+
+Water.prototype = Object.create(THREE.Mesh.prototype);
+Water.prototype.constructor = Water;
+
+Water.prototype.fillTexture = function (texture, WIDTH) {
+ var simplex = new SimplexNoise();
+
+ var waterMaxHeight = 30;
+
+ function noise(x, y, z) {
+ var multR = waterMaxHeight;
+ var mult = 0.025;
+ var r = 0;
+ for (var i = 0; i < 15; i++) {
+ r += multR * simplex.noise(x * mult, y * mult);
+ multR *= 0.53 + 0.025 * i;
+ mult *= 1.25;
+ }
+ return r;
+ }
+
+ var pixels = texture.image.data;
+ var p = 0;
+ for (var j = 0; j < WIDTH; j++) {
+ for (var i = 0; i < WIDTH; i++) {
+ var x = i * 128 / WIDTH;
+ var y = j * 128 / WIDTH;
+ pixels[p + 0] = noise(x, y, 123.4);
+ pixels[p + 1] = 0;
+ pixels[p + 2] = 0;
+ pixels[p + 3] = 1;
+ p += 4;
+ }
+ }
+};
+
+Water.prototype.update = function () {
+ var heightmapVariable = this.heightmapVariable;
+ var gpuCompute = this.gpuCompute;
+ var waterUniforms = this.waterUniforms;
+
+ var uniforms = heightmapVariable.material.uniforms;
+ uniforms.mousePos.value.set(10000, 10000);
+
+ gpuCompute.compute();
+
+ waterUniforms.heightmap.value = gpuCompute.getCurrentRenderTarget(heightmapVariable).texture;
+};
+
+export default Water;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/water/shader/heightmap_fragment.glsl b/ShadowEditor.Core/src/object/water/shader/heightmap_fragment.glsl
new file mode 100644
index 00000000..76c5ba43
--- /dev/null
+++ b/ShadowEditor.Core/src/object/water/shader/heightmap_fragment.glsl
@@ -0,0 +1,30 @@
+#include
+uniform vec2 mousePos;
+uniform float mouseSize;
+uniform float viscosityConstant;
+#define deltaTime ( 1.0 / 60.0 )
+#define GRAVITY_CONSTANT ( resolution.x * deltaTime * 3.0 )
+void main() {
+ vec2 cellSize = 1.0 / resolution.xy;
+ vec2 uv = gl_FragCoord.xy * cellSize;
+ // heightmapValue.x == height
+ // heightmapValue.y == velocity
+ // heightmapValue.z, heightmapValue.w not used
+ vec4 heightmapValue = texture2D( heightmap, uv );
+ // Get neighbours
+ vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
+ vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
+ vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
+ vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
+ float sump = north.x + south.x + east.x + west.x - 4.0 * heightmapValue.x;
+ float accel = sump * GRAVITY_CONSTANT;
+ // Dynamics
+ heightmapValue.y += accel;
+ heightmapValue.x += heightmapValue.y * deltaTime;
+ // Viscosity
+ heightmapValue.x += sump * viscosityConstant;
+ // Mouse influence
+ float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI );
+ heightmapValue.x += cos( mousePhase ) + 1.0;
+ gl_FragColor = heightmapValue;
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/water/shader/smooth_fragment.glsl b/ShadowEditor.Core/src/object/water/shader/smooth_fragment.glsl
new file mode 100644
index 00000000..b132703d
--- /dev/null
+++ b/ShadowEditor.Core/src/object/water/shader/smooth_fragment.glsl
@@ -0,0 +1,14 @@
+uniform sampler2D texture;
+
+void main() {
+ vec2 cellSize = 1.0 / resolution.xy;
+ vec2 uv = gl_FragCoord.xy * cellSize;
+ // Computes the mean of texel and 4 neighbours
+ vec4 textureValue = texture2D( texture, uv );
+ textureValue += texture2D( texture, uv + vec2( 0.0, cellSize.y ) );
+ textureValue += texture2D( texture, uv + vec2( 0.0, - cellSize.y ) );
+ textureValue += texture2D( texture, uv + vec2( cellSize.x, 0.0 ) );
+ textureValue += texture2D( texture, uv + vec2( - cellSize.x, 0.0 ) );
+ textureValue /= 5.0;
+ gl_FragColor = textureValue;
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/object/water/shader/water_vertex.glsl b/ShadowEditor.Core/src/object/water/shader/water_vertex.glsl
new file mode 100644
index 00000000..55e09af0
--- /dev/null
+++ b/ShadowEditor.Core/src/object/water/shader/water_vertex.glsl
@@ -0,0 +1,52 @@
+uniform sampler2D heightmap;
+#define PHONG
+varying vec3 vViewPosition;
+#ifndef FLAT_SHADED
+ varying vec3 vNormal;
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+void main() {
+ vec2 cellSize = vec2( 1.0 / WIDTH, 1.0 / WIDTH );
+ #include
+ #include
+ #include
+ // # include
+ // Compute normal from heightmap
+ vec3 objectNormal = vec3(
+ ( texture2D( heightmap, uv + vec2( - cellSize.x, 0 ) ).x - texture2D( heightmap, uv + vec2( cellSize.x, 0 ) ).x ) * WIDTH / BOUNDS,
+ ( texture2D( heightmap, uv + vec2( 0, - cellSize.y ) ).x - texture2D( heightmap, uv + vec2( 0, cellSize.y ) ).x ) * WIDTH / BOUNDS,
+ 1.0 );
+ //
+ #include
+ #include
+ #include
+ #include
+#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
+ vNormal = normalize( transformedNormal );
+#endif
+ //# include
+ float heightValue = texture2D( heightmap, uv ).x;
+ vec3 transformed = vec3( position.x, position.y, heightValue );
+ //
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ vViewPosition = - mvPosition.xyz;
+ #include
+ #include
+ #include
+}
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/physics/PhysicsData.js b/ShadowEditor.Core/src/physics/PhysicsData.js
new file mode 100644
index 00000000..32259cbe
--- /dev/null
+++ b/ShadowEditor.Core/src/physics/PhysicsData.js
@@ -0,0 +1,37 @@
+/**
+ * Object3D.userData.physics表示的物理数据结构
+ */
+var PhysicsData = {
+ // 物理形状
+ // btBoxShape: 正方体
+ // btBvhTriangleMeshShape: 三角形
+ // btCapsuleShape: 胶囊
+ // btCapsuleShapeX: x轴胶囊
+ // btCapsuleShapeY: y轴胶囊
+ // btCapsuleShapeZ: z轴胶囊
+ // btCompoundShape: 复合形状
+ // btConeShape: 圆锥体
+ // btConeShapeX: x轴圆椎体
+ // btConeShapeZ: z轴圆椎体
+ // btConvexHullShape: 凸包
+ // btConvexTriangleMeshShape: 凸三角形
+ // btCylinderShape: 圆柱体
+ // btCylinderShapeX: x轴圆柱体
+ // btCylinderShapeZ: z轴圆柱体
+ // btHeightfieldTerrainShape: 灰阶高程地形
+ // btSphereShape: 球体
+ // btStaticPlaneShape: 静态平板
+ shape: 'btBoxShape',
+
+ // 质量
+ mass: 1,
+
+ // 惯性
+ inertia: {
+ x: 0,
+ y: 0,
+ z: 0,
+ }
+};
+
+export default PhysicsData;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/physics/PlysicsUtils.js b/ShadowEditor.Core/src/physics/PlysicsUtils.js
new file mode 100644
index 00000000..474efb13
--- /dev/null
+++ b/ShadowEditor.Core/src/physics/PlysicsUtils.js
@@ -0,0 +1,81 @@
+import { UI } from '../third_party';
+import PhysicsData from './PhysicsData';
+
+/**
+ * 物理工具
+ */
+var PlysicsUtils = {
+ /**
+ * 为Object3D对象添加刚体数据结构
+ */
+ addRigidBodyData: function (obj) {
+ if (!(obj instanceof THREE.Mesh)) {
+ UI.msg('暂时只能为THREE.Mesh添加刚体组件。');
+ return false;
+ }
+ if (obj.geometry instanceof THREE.PlaneBufferGeometry) {
+ obj.userData.physics = Object.assign({}, PhysicsData, {
+ shape: 'btStaticPlaneShape',
+ mass: 0
+ });
+ } else if (obj.geometry instanceof THREE.BoxBufferGeometry) {
+ obj.userData.physics = Object.assign({}, PhysicsData, {
+ shape: 'btBoxShape',
+ });
+ } else if (obj.geometry instanceof THREE.SphereBufferGeometry) {
+ obj.userData.physics = Object.assign({}, PhysicsData, {
+ shape: 'btSphereShape',
+ });
+ } else {
+ UI.msg(`暂不支持为${obj.geometry.constructor.name}几何体添加刚体组件。`);
+ return false;
+ }
+ },
+
+ /**
+ * 为Object3D对象添加刚体组件
+ */
+ createRigidBody: function (obj, margin) {
+ margin = margin || 0.05;
+
+ var position = obj.position;
+ var quaternion = obj.quaternion;
+ var scale = obj.scale;
+
+ var mass = obj.userData.physics.mass;
+ var inertia = obj.userData.physics.inertia;
+
+ var transform = new Ammo.btTransform();
+ transform.setIdentity();
+ transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
+ transform.setRotation(new Ammo.btQuaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w));
+ var motionState = new Ammo.btDefaultMotionState(transform);
+
+ var shape = null;
+ if (obj.geometry instanceof THREE.PlaneBufferGeometry) {
+ var x = obj.geometry.parameters.width * scale.x;
+ var y = obj.geometry.parameters.height * scale.y;
+ shape = new Ammo.btBoxShape(new Ammo.btVector3(x * 0.5, y * 0.5, 0));
+ } else if (obj.geometry instanceof THREE.BoxBufferGeometry) {
+ var x = obj.geometry.parameters.width * scale.x;
+ var y = obj.geometry.parameters.height * scale.y;
+ var z = obj.geometry.parameters.depth * scale.z;
+ shape = new Ammo.btBoxShape(new Ammo.btVector3(x * 0.5, y * 0.5, z * 0.5));
+ } else if (obj.geometry instanceof THREE.SphereBufferGeometry) {
+ var radius = obj.geometry.parameters.radius;
+ shape = new Ammo.btSphereShape(radius);
+ } else {
+ console.warn(`PlysicsUtils: 无法为${obj.name}创建刚体组件。`);
+ return null;
+ }
+
+ shape.setMargin(margin);
+ var localInertia = new Ammo.btVector3(inertia.x, inertia.y, inertia.z);
+ shape.calculateLocalInertia(mass, localInertia);
+
+ var info = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
+ return new Ammo.btRigidBody(info);
+ }
+};
+
+export default PlysicsUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/Player.js b/ShadowEditor.Core/src/player/Player.js
new file mode 100644
index 00000000..c05a5816
--- /dev/null
+++ b/ShadowEditor.Core/src/player/Player.js
@@ -0,0 +1,163 @@
+import { UI } from '../third_party';
+import Converter from '../serialization/Converter';
+
+import PlayerLoader from './component/PlayerLoader';
+import PlayerEvent from './component/PlayerEvent';
+import PlayerAudio from './component/PlayerAudio';
+import PlayerAnimation from './component/PlayerAnimation';
+import PlayerPhysics from './component/PlayerPhysics';
+
+/**
+ * 播放器
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ */
+function Player(options) {
+ UI.Control.call(this, options);
+ this.app = options.app;
+
+ this.scene = null;
+ this.camera = null;
+ this.renderer = null;
+
+ this.loader = new PlayerLoader(this.app);
+ this.event = new PlayerEvent(this.app);
+ this.audio = new PlayerAudio(this.app);
+ this.animation = new PlayerAnimation(this.app);
+ this.physics = new PlayerPhysics(this.app);
+
+ this.isPlaying = false;
+ this.clock = new THREE.Clock(false);
+};
+
+Player.prototype = Object.create(UI.Control.prototype);
+Player.prototype.constructor = Player;
+
+Player.prototype.render = function () {
+ (UI.create({
+ xtype: 'div',
+ parent: this.parent,
+ id: 'player',
+ scope: this.id,
+ cls: 'Panel player',
+ style: {
+ display: 'none'
+ }
+ })).render();
+};
+
+/**
+ * 启动播放器
+ */
+Player.prototype.start = function () {
+ if (this.isPlaying) {
+ return;
+ }
+ this.isPlaying = true;
+
+ var container = UI.get('player', this.id);
+ container.dom.style.display = '';
+
+ var jsons = (new Converter()).toJSON({
+ options: this.app.options,
+ scene: this.app.editor.scene,
+ camera: this.app.editor.camera,
+ renderer: this.app.editor.renderer,
+ scripts: this.app.editor.scripts,
+ animation: this.app.editor.animation,
+ });
+
+ this.loader.create(jsons).then(obj => {
+ this.initPlayer(obj);
+
+ this.event.create(this.scene, this.camera, this.renderer, obj.scripts);
+ this.audio.create(this.scene, this.camera, this.renderer, this.loader);
+ this.animation.create(this.scene, this.camera, this.renderer, obj.animation);
+ this.physics.create(this.scene, this.camera, this.renderer);
+
+ this.clock.start();
+ this.event.init();
+ this.renderScene();
+ this.event.start();
+
+ requestAnimationFrame(this.animate.bind(this));
+ });
+};
+
+/**
+ * 停止播放器
+ */
+Player.prototype.stop = function () {
+ if (!this.isPlaying) {
+ return;
+ }
+ this.isPlaying = false;
+
+ this.event.stop();
+
+ this.loader.dispose();
+ this.event.dispose();
+ this.audio.dispose();
+ this.animation.dispose();
+ this.physics.dispose();
+
+ var container = UI.get('player', this.id);
+ container.dom.removeChild(this.renderer.domElement);
+ container.dom.style.display = 'none';
+
+ this.scene = null;
+ this.camera = null;
+ this.renderer = null;
+
+ this.clock.stop();
+};
+
+/**
+ * 初始化播放器
+ * @param {*} obj
+ */
+Player.prototype.initPlayer = function (obj) {
+ var container = UI.get('player', this.id);
+ var editor = this.app.editor;
+
+ this.camera = obj.camera || new THREE.PerspectiveCamera(
+ editor.DEFAULT_CAMERA.fov,
+ container.dom.clientWidth / container.dom.clientHeight,
+ editor.DEFAULT_CAMERA.near,
+ editor.DEFAULT_CAMERA.far
+ );
+ this.camera.updateProjectionMatrix();
+
+ this.renderer = obj.renderer || new THREE.WebGLRenderer({
+ antialias: true
+ });;
+ this.renderer.setSize(container.dom.clientWidth, container.dom.clientHeight);
+ container.dom.appendChild(this.renderer.domElement);
+
+ var listener = obj.audioListener || new THREE.AudioListener();
+ this.camera.add(listener);
+
+ this.scene = obj.scene || new THREE.Scene();
+};
+
+Player.prototype.renderScene = function () {
+ this.renderer.render(this.scene, this.camera);
+};
+
+Player.prototype.animate = function () {
+ if (!this.isPlaying) {
+ return;
+ }
+
+ this.renderScene();
+
+ var deltaTime = this.clock.getDelta();
+
+ this.event.update(this.clock, deltaTime);
+ this.animation.update(this.clock, deltaTime);
+ this.physics.update(this.clock, deltaTime);
+
+ requestAnimationFrame(this.animate.bind(this));
+};
+
+export default Player;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/animator/MMDAnimator.js b/ShadowEditor.Core/src/player/animator/MMDAnimator.js
new file mode 100644
index 00000000..b401cc32
--- /dev/null
+++ b/ShadowEditor.Core/src/player/animator/MMDAnimator.js
@@ -0,0 +1,35 @@
+import PlayerComponent from '../component/PlayerComponent';
+
+/**
+ * MMD动画控制器
+ * @param {*} app 应用
+ */
+function MMDAnimator(app) {
+ PlayerComponent.call(this, app);
+
+ this.helper = new THREE.MMDHelper();
+}
+
+MMDAnimator.prototype = Object.create(PlayerComponent.prototype);
+MMDAnimator.prototype.constructor = MMDAnimator;
+
+MMDAnimator.prototype.create = function (scene, camera, renderer, animations) {
+ scene.traverse(n => {
+ if (n instanceof THREE.SkinnedMesh && (n.userData.Type === 'pmd' || n.userData.Type === 'pmx')) {
+ this.helper.add(n);
+ this.helper.setAnimation(n);
+ this.helper.setPhysics(n);
+ this.helper.unifyAnimationDuration();
+ }
+ });
+};
+
+MMDAnimator.prototype.update = function (clock, deltaTime) {
+ this.helper.animate(deltaTime);
+};
+
+MMDAnimator.prototype.dispose = function () {
+ this.helper.meshes.length = 0;
+};
+
+export default MMDAnimator;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/animator/ParticleAnimator.js b/ShadowEditor.Core/src/player/animator/ParticleAnimator.js
new file mode 100644
index 00000000..22187f1c
--- /dev/null
+++ b/ShadowEditor.Core/src/player/animator/ParticleAnimator.js
@@ -0,0 +1,37 @@
+import PlayerComponent from '../component/PlayerComponent';
+import Ease from '../../animation/Ease';
+
+/**
+ * 粒子动画控制器
+ * @param {*} app 应用
+ */
+function ParticleAnimator(app) {
+ PlayerComponent.call(this, app);
+}
+
+ParticleAnimator.prototype = Object.create(PlayerComponent.prototype);
+ParticleAnimator.prototype.constructor = ParticleAnimator;
+
+ParticleAnimator.prototype.create = function (scene, camera, renderer) {
+ this.scene = scene;
+};
+
+ParticleAnimator.prototype.update = function (clock, deltaTime, time) {
+ var elapsed = clock.getElapsedTime();
+
+ this.scene.children.forEach(n => {
+ if (n.userData.type === 'Fire') {
+ n.userData.fire.update(elapsed);
+ } else if (n.userData.type === 'Smoke') {
+ n.update(elapsed);
+ } if (n.userData.type === 'ParticleEmitter') {
+ n.userData.group.tick(deltaTime);
+ }
+ });
+};
+
+ParticleAnimator.prototype.dispose = function () {
+ this.scene = null;
+};
+
+export default ParticleAnimator;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/animator/TweenAnimator.js b/ShadowEditor.Core/src/player/animator/TweenAnimator.js
new file mode 100644
index 00000000..d03991b9
--- /dev/null
+++ b/ShadowEditor.Core/src/player/animator/TweenAnimator.js
@@ -0,0 +1,80 @@
+import PlayerComponent from '../component/PlayerComponent';
+import Ease from '../../animation/Ease';
+
+/**
+ * 补间动画控制器
+ * @param {*} app 应用
+ */
+function TweenAnimator(app) {
+ PlayerComponent.call(this, app);
+}
+
+TweenAnimator.prototype = Object.create(PlayerComponent.prototype);
+TweenAnimator.prototype.constructor = TweenAnimator;
+
+TweenAnimator.prototype.create = function (scene, camera, renderer, animations) {
+ this.scene = scene;
+ this.animations = animations;
+};
+
+TweenAnimator.prototype.update = function (clock, deltaTime, time) {
+ this.animations.forEach(n => {
+ n.animations.forEach(m => {
+ this.tweenObject(m, time);
+ });
+ });
+};
+
+TweenAnimator.prototype.tweenObject = function (animation, time) {
+ // 条件判断
+ if (animation.type !== 'Tween' || time < animation.beginTime || time > animation.endTime || animation.target == null) {
+ return;
+ }
+
+ // 获取对象
+ var target = this.scene.getObjectByProperty('uuid', animation.target);
+ if (target == null) {
+ console.warn(`Player: 场景中不存在uuid为${animation.target}的物体。`);
+ return;
+ }
+
+ // 获取插值函数
+ var ease = Ease[animation.ease];
+ if (ease == null) {
+ console.warn(`Player: 不存在名称为${animation.ease}的插值函数。`);
+ return;
+ }
+
+ var result = ease((time - animation.beginTime) / (animation.endTime - animation.beginTime));
+
+ var positionX = animation.beginPositionX + (animation.endPositionX - animation.beginPositionX) * result;
+ var positionY = animation.beginPositionY + (animation.endPositionY - animation.beginPositionY) * result;
+ var positionZ = animation.beginPositionZ + (animation.endPositionZ - animation.beginPositionZ) * result;
+
+ var rotationX = animation.beginRotationX + (animation.endRotationX - animation.beginRotationX) * result;
+ var rotationY = animation.beginRotationY + (animation.endRotationY - animation.beginRotationY) * result;
+ var rotationZ = animation.beginRotationZ + (animation.endRotationZ - animation.beginRotationZ) * result;
+
+ var scaleX = animation.beginScaleX + (animation.endScaleX - animation.beginScaleX) * result;
+ var scaleY = animation.beginScaleY + (animation.endScaleY - animation.beginScaleY) * result;
+ var scaleZ = animation.beginScaleZ + (animation.endScaleZ - animation.beginScaleZ) * result;
+
+ target.position.x = positionX;
+ target.position.y = positionY;
+ target.position.z = positionZ;
+
+ target.rotation.x = rotationX;
+ target.rotation.y = rotationY;
+ target.rotation.z = rotationZ;
+
+ target.scale.x = scaleX;
+ target.scale.y = scaleY;
+ target.scale.z = scaleZ;
+};
+
+TweenAnimator.prototype.dispose = function () {
+ this.scene = null;
+ this.animations = null;
+};
+
+export default TweenAnimator;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerAnimation.js b/ShadowEditor.Core/src/player/component/PlayerAnimation.js
new file mode 100644
index 00000000..383d0612
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerAnimation.js
@@ -0,0 +1,94 @@
+import PlayerComponent from './PlayerComponent';
+import Ease from '../../animation/Ease';
+
+import TweenAnimator from '../animator/TweenAnimator';
+import MMDAnimator from '../animator/MMDAnimator';
+import ParticleAnimator from '../animator/ParticleAnimator';
+
+/**
+ * 播放器动画
+ * @param {*} app 应用
+ */
+function PlayerAnimation(app) {
+ PlayerComponent.call(this, app);
+
+ this.maxTime = 0; // 最大动画时间(单位:秒)
+ this.currentTime = 0; // 当前动画时间(单位:秒)
+ this.animations = null;
+
+ this.animators = [
+ new TweenAnimator(this.app),
+ new MMDAnimator(this.app),
+ new ParticleAnimator(this.app)
+ ];
+}
+
+PlayerAnimation.prototype = Object.create(PlayerComponent.prototype);
+PlayerAnimation.prototype.constructor = PlayerAnimation;
+
+PlayerAnimation.prototype.create = function (scene, camera, renderer, animations) {
+ this.maxTime = 0;
+ this.currentTime = 0;
+ this.scene = scene;
+ this.camera = camera;
+ this.renderer = renderer;
+ this.animations = animations;
+
+ this.maxTime = this.calculateMaxTime();
+
+ this.animators.forEach(n => {
+ n.create(scene, camera, renderer, animations);
+ });
+
+ this.app.call(`resetAnimation`, this);
+ this.app.call(`startAnimation`, this);
+ this.app.on(`animationTime.${this.id}`, this.updateTime.bind(this));
+};
+
+PlayerAnimation.prototype.calculateMaxTime = function () {
+ var maxTime = 0;
+
+ this.animations.forEach(n => {
+ n.animations.forEach(m => {
+ if (m.endTime > maxTime) {
+ maxTime = m.endTime;
+ }
+ });
+ });
+
+ return maxTime;
+};
+
+PlayerAnimation.prototype.updateTime = function (time) {
+ this.currentTime = time;
+};
+
+PlayerAnimation.prototype.update = function (clock, deltaTime) {
+ this.animators.forEach(n => {
+ n.update(clock, deltaTime, this.currentTime);
+ });
+
+ // 超过最大动画时间,重置动画
+ if (this.currentTime > this.maxTime) {
+ this.app.call(`resetAnimation`, this.id);
+ this.app.call(`startAnimation`, this.id);
+ }
+};
+
+PlayerAnimation.prototype.dispose = function () {
+ this.maxTime = 0;
+ this.currentTime = 0;
+ this.scene = null;
+ this.camera = null;
+ this.renderer = null;
+ this.animations = null;
+
+ this.animators.forEach(n => {
+ n.dispose();
+ });
+
+ this.app.on(`animationTime.${this.id}`, null);
+ this.app.call(`resetAnimation`, this);
+};
+
+export default PlayerAnimation;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerAudio.js b/ShadowEditor.Core/src/player/component/PlayerAudio.js
new file mode 100644
index 00000000..5d1571c6
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerAudio.js
@@ -0,0 +1,50 @@
+import PlayerComponent from './PlayerComponent';
+
+/**
+ * 播放器音频
+ * @param {*} app 应用
+ */
+function PlayerAudio(app) {
+ PlayerComponent.call(this, app);
+
+ this.audios = [];
+}
+
+PlayerAudio.prototype = Object.create(PlayerComponent.prototype);
+PlayerAudio.prototype.constructor = PlayerAudio;
+
+PlayerAudio.prototype.create = function (scene, camera, renderer, loader) {
+ this.audios = [];
+
+ scene.traverse(n => {
+ if (n instanceof THREE.Audio) {
+ var buffer = loader.getAsset(n.userData.Url);
+
+ if (buffer === undefined) {
+ this.app.error(`PlayerAudio: 加载背景音乐失败。`);
+ return;
+ }
+
+ n.setBuffer(buffer);
+
+ if (n.userData.autoplay) {
+ n.autoplay = n.userData.autoplay;
+ n.play();
+ }
+
+ this.audios.push(n);
+ }
+ });
+};
+
+PlayerAudio.prototype.dispose = function () {
+ this.audios.forEach(n => {
+ if (n.isPlaying) {
+ n.stop();
+ }
+ });
+
+ this.audios.length = 0;
+};
+
+export default PlayerAudio;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerComponent.js b/ShadowEditor.Core/src/player/component/PlayerComponent.js
new file mode 100644
index 00000000..fe02f3c1
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerComponent.js
@@ -0,0 +1,43 @@
+var ID = -1;
+
+/**
+ * 播放器组件
+ * @param {*} app 应用
+ */
+function PlayerComponent(app) {
+ this.id = `PlayerComponent${ID--}`
+ this.app = app;
+}
+
+/**
+ * 创建
+ * @param {*} scene
+ * @param {*} camera
+ * @param {*} renderer
+ * @param {*} others
+ */
+PlayerComponent.prototype.create = function (scene, camera, renderer, others) {
+
+};
+
+/**
+ * 更新
+ * @param {*} clock
+ * @param {*} deltaTime
+ */
+PlayerComponent.prototype.update = function (clock, deltaTime) {
+
+};
+
+/**
+ * 析构
+ * @param {*} scene
+ * @param {*} camera
+ * @param {*} renderer
+ * @param {*} others
+ */
+PlayerComponent.prototype.dispose = function (scene, camera, renderer, others) {
+
+};
+
+export default PlayerComponent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerEvent.js b/ShadowEditor.Core/src/player/component/PlayerEvent.js
new file mode 100644
index 00000000..7bfb20c3
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerEvent.js
@@ -0,0 +1,167 @@
+import PlayerComponent from './PlayerComponent';
+
+/**
+ * 播放器事件
+ * @param {*} app 应用
+ */
+function PlayerEvent(app) {
+ PlayerComponent.call(this, app);
+}
+
+PlayerEvent.prototype = Object.create(PlayerComponent.prototype);
+PlayerEvent.prototype.constructor = PlayerEvent;
+
+PlayerEvent.prototype.create = function (scene, camera, renderer, scripts) {
+ this.scene = scene;
+ this.camera = camera;
+ this.renderer = renderer;
+ this.scripts = scripts;
+
+ var dom = renderer.domElement;
+
+ this.events = Object.keys(scripts).map(uuid => {
+ var script = scripts[uuid];
+ return (new Function(
+ 'scene',
+ 'camera',
+ 'renderer',
+ script.source + `
+ var init = init || null;
+ var start = start || null;
+ var update = update || null;
+ var stop = stop || null;
+ var onClick = onClick || null;
+ var onDblClick = onDblClick || null;
+ var onKeyDown = onKeyDown || null;
+ var onKeyUp = onKeyUp || null;
+ var onMouseDown = onMouseDown || null;
+ var onMouseMove = onMouseMove || null;
+ var onMouseUp = onMouseUp || null;
+ var onMouseWheel = onMouseWheel || null;
+ var onResize = onResize || null;
+ return { init, start, update, stop, onClick, onDblClick, onKeyDown, onKeyUp, onMouseDown, onMouseMove, onMouseUp, onMouseWheel, onResize };
+ `
+ )).call(scene, scene, camera, renderer);
+ });
+
+ this.events.forEach(n => {
+ if (typeof (n.onClick) === 'function') {
+ dom.addEventListener('click', n.onClick.bind(this.scene));
+ }
+ if (typeof (n.onDblClick) === 'function') {
+ dom.addEventListener('dblclick', n.onDblClick.bind(this.scene));
+ }
+ if (typeof (n.onKeyDown) === 'function') {
+ dom.addEventListener('keydown', n.onKeyDown.bind(this.scene));
+ }
+ if (typeof (n.onKeyUp) === 'function') {
+ dom.addEventListener('keyup', n.onKeyUp.bind(this.scene));
+ }
+ if (typeof (n.onMouseDown) === 'function') {
+ dom.addEventListener('mousedown', n.onMouseDown.bind(this.scene));
+ }
+ if (typeof (n.onMouseMove) === 'function') {
+ dom.addEventListener('mousemove', n.onMouseMove.bind(this.scene));
+ }
+ if (typeof (n.onMouseUp) === 'function') {
+ dom.addEventListener('mouseup', n.onMouseUp.bind(this.scene));
+ }
+ if (typeof (n.onMouseWheel) === 'function') {
+ dom.addEventListener('mousewheel', n.onMouseWheel.bind(this.scene));
+ }
+ if (typeof (n.onResize) === 'function') {
+ window.addEventListener('resize', n.onResize.bind(this.scene));
+ }
+ });
+};
+
+/**
+ * 场景载入前执行一次
+ */
+PlayerEvent.prototype.init = function () {
+ this.events.forEach(n => {
+ if (typeof (n.init) === 'function') {
+ n.init();
+ }
+ });
+};
+
+/**
+ * 场景载入后执行一次
+ */
+PlayerEvent.prototype.start = function () {
+ this.events.forEach(n => {
+ if (typeof (n.start) === 'function') {
+ n.start();
+ }
+ });
+};
+
+/**
+ * 运行期间每帧都要执行
+ * @param {*} clock
+ * @param {*} deltaTime
+ */
+PlayerEvent.prototype.update = function (clock, deltaTime) {
+ this.events.forEach(n => {
+ if (typeof (n.update) === 'function') {
+ n.update(clock, deltaTime);
+ }
+ });
+};
+
+/**
+ * 程序结束运行后执行一次
+ */
+PlayerEvent.prototype.stop = function () {
+ this.events.forEach(n => {
+ if (typeof (n.stop) === 'function') {
+ n.stop();
+ }
+ });
+};
+
+/**
+ * 析构PlayerEvent
+ */
+PlayerEvent.prototype.dispose = function () {
+ var dom = this.renderer.domElement;
+
+ this.events.forEach(n => {
+ if (typeof (n.onClick) === 'function') {
+ dom.removeEventListener('click', n.onClick.bind(this.scene));
+ }
+ if (typeof (n.onDblClick) === 'function') {
+ dom.removeEventListener('dblclick', n.onDblClick.bind(this.scene));
+ }
+ if (typeof (n.onKeyDown) === 'function') {
+ dom.removeEventListener('keydown', n.onKeyDown.bind(this.scene));
+ }
+ if (typeof (n.onKeyUp) === 'function') {
+ dom.removeEventListener('keyup', n.onKeyUp.bind(this.scene));
+ }
+ if (typeof (n.onMouseDown) === 'function') {
+ dom.removeEventListener('mousedown', n.onMouseDown.bind(this.scene));
+ }
+ if (typeof (n.onMouseMove) === 'function') {
+ dom.removeEventListener('mousemove', n.onMouseMove.bind(this.scene));
+ }
+ if (typeof (n.onMouseUp) === 'function') {
+ dom.removeEventListener('mouseup', n.onMouseUp.bind(this.scene));
+ }
+ if (typeof (n.onMouseWheel) === 'function') {
+ dom.removeEventListener('mousewheel', n.onMouseWheel.bind(this.scene));
+ }
+ if (typeof (n.onResize) === 'function') {
+ window.removeEventListener('resize', n.onResize.bind(this.scene));
+ }
+ });
+
+ this.scene = null;
+ this.camera = null;
+ this.renderer = null;
+ this.scripts = null;
+ this.events.length = 0;
+};
+
+export default PlayerEvent;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerLoader.js b/ShadowEditor.Core/src/player/component/PlayerLoader.js
new file mode 100644
index 00000000..2e08e344
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerLoader.js
@@ -0,0 +1,70 @@
+import PlayerComponent from './PlayerComponent';
+import Converter from '../../serialization/Converter';
+
+/**
+ * 播放器事件
+ * @param {*} app 应用
+ */
+function PlayerLoader(app) {
+ PlayerComponent.call(this, app);
+
+ this.assets = {};
+}
+
+PlayerLoader.prototype = Object.create(PlayerComponent.prototype);
+PlayerLoader.prototype.constructor = PlayerLoader;
+
+PlayerLoader.prototype.create = function (jsons) {
+ var promise = (new Converter()).fromJson(jsons, {
+ server: this.app.options.server
+ });
+
+ return new Promise(resolve => {
+ promise.then(obj => {
+ this.scene = obj.scene || new THREE.Scene();
+ this.load().then(() => {
+ resolve(obj);
+ });
+ });
+ });
+};
+
+PlayerLoader.prototype.load = function () {
+ this.assets = {};
+ var promises = [];
+
+ this.scene.traverse(n => {
+ if (n instanceof THREE.Audio) {
+ promises.push(new Promise(resolve => {
+ var loader = new THREE.AudioLoader();
+
+ loader.load(this.app.options.server + n.userData.Url, buffer => {
+ this.assets[n.userData.Url] = buffer;
+ resolve();
+ }, undefined, () => {
+ this.app.error(`PlayerLoader: ${n.userData.Url}下载失败。`);
+ resolve();
+ });
+ }));
+ }
+ });
+
+ if (promises.length > 0) {
+ return Promise.all(promises);
+ } else {
+ return new Promise(resolve => {
+ resolve();
+ });
+ }
+};
+
+PlayerLoader.prototype.getAsset = function (url) {
+ return this.assets[url];
+};
+
+PlayerLoader.prototype.dispose = function () {
+ this.assets = {};
+ this.scene = null;
+};
+
+export default PlayerLoader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/player/component/PlayerPhysics.js b/ShadowEditor.Core/src/player/component/PlayerPhysics.js
new file mode 100644
index 00000000..3965fbb6
--- /dev/null
+++ b/ShadowEditor.Core/src/player/component/PlayerPhysics.js
@@ -0,0 +1,99 @@
+import PlayerComponent from './PlayerComponent';
+import PlysicsUtils from '../../physics/PlysicsUtils';
+
+/**
+ * 播放器物理
+ * @param {*} app 应用
+ */
+function PlayerPhysics(app) {
+ PlayerComponent.call(this, app);
+
+ // 各种参数
+ var gravityConstant = -9.8; // 重力常数
+ this.margin = 0.05; // 两个物体之间最小间距
+
+ // 物理环境配置
+ var collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); // 软体刚体碰撞配置
+ var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); // 碰撞调度器
+ var broadphase = new Ammo.btDbvtBroadphase(); // dbvt粗测
+ var solver = new Ammo.btSequentialImpulseConstraintSolver(); // 顺序脉冲约束求解器
+ var softBodySolver = new Ammo.btDefaultSoftBodySolver(); // 默认软体求解器
+
+ this.world = new Ammo.btSoftRigidDynamicsWorld(
+ dispatcher,
+ broadphase,
+ solver,
+ collisionConfiguration,
+ softBodySolver
+ );
+
+ var gravity = new Ammo.btVector3(0, gravityConstant, 0);
+ this.world.setGravity(gravity);
+ this.world.getWorldInfo().set_m_gravity(gravity);
+
+ this.transformAux1 = new Ammo.btTransform();
+ this.rigidBodies = [];
+}
+
+PlayerPhysics.prototype = Object.create(PlayerComponent.prototype);
+PlayerPhysics.prototype.constructor = PlayerPhysics;
+
+PlayerPhysics.prototype.create = function (scene, camera, renderer) {
+ this.scene = scene;
+
+ this.scene.traverse(n => {
+ if (n.userData && n.userData.physics) {
+ var body = PlysicsUtils.createRigidBody(n, this.margin);
+ if (body) {
+ n.userData.physics.body = body;
+ this.world.addRigidBody(body);
+
+ if (n.userData.physics.mass > 0) {
+ this.rigidBodies.push(n);
+ body.setActivationState(4);
+ }
+ }
+ }
+ });
+};
+
+PlayerPhysics.prototype.update = function (clock, deltaTime) {
+ var rigidBodies = this.rigidBodies;
+
+ this.world.stepSimulation(deltaTime, 100);
+
+ for (var i = 0, l = rigidBodies.length; i < l; i++) {
+ var objThree = rigidBodies[i];
+ var objPhys = objThree.userData.physics.body;
+ if (!objPhys) {
+ continue;
+ }
+ var ms = objPhys.getMotionState();
+ if (ms) {
+ ms.getWorldTransform(this.transformAux1);
+ var p = this.transformAux1.getOrigin();
+ var q = this.transformAux1.getRotation();
+ objThree.position.set(p.x(), p.y(), p.z());
+ objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
+ }
+ }
+};
+
+PlayerPhysics.prototype.dispose = function () {
+ this.rigidBodies.forEach(n => {
+ var body = n.userData.physics.body;
+ this.world.removeRigidBody(body);
+ });
+
+ this.rigidBodies.length = 0;
+
+ this.scene.traverse(n => {
+ if (n.userData && n.userData.physics && n.userData.physics) {
+ n.userData.physics.body = null;
+ }
+ });
+
+ this.scene = null;
+};
+
+export default PlayerPhysics;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/polyfills.js b/ShadowEditor.Core/src/polyfills.js
new file mode 100644
index 00000000..fb61b3d1
--- /dev/null
+++ b/ShadowEditor.Core/src/polyfills.js
@@ -0,0 +1,6 @@
+window.URL = window.URL || window.webkitURL;
+window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
+
+Number.prototype.format = function () {
+ return this.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
+};
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/BaseSerializer.js b/ShadowEditor.Core/src/serialization/BaseSerializer.js
new file mode 100644
index 00000000..474e767d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/BaseSerializer.js
@@ -0,0 +1,40 @@
+import Metadata from './Metadata';
+
+var ID = -1;
+
+/**
+ * 序列化器基类
+ * @author tengge / https://github.com/tengge1
+ */
+function BaseSerializer() {
+ this.id = 'BaseSerializer' + ID--;
+ this.metadata = Object.assign({}, Metadata, {
+ generator: this.constructor.name
+ });
+}
+
+/**
+ *对象转json
+ * @param {*} obj 对象
+ */
+BaseSerializer.prototype.toJSON = function (obj) {
+ var json = {
+ metadata: this.metadata
+ };
+ return json;
+};
+
+/**
+ * json转对象
+ * @param {*} json json对象
+ * @param {*} parent 父对象
+ */
+BaseSerializer.prototype.fromJSON = function (json, parent) {
+ if (parent) {
+ return parent;
+ }
+
+ return {};
+};
+
+export default BaseSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/Converter.js b/ShadowEditor.Core/src/serialization/Converter.js
new file mode 100644
index 00000000..f7d5254d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/Converter.js
@@ -0,0 +1,380 @@
+import BaseSerializer from './BaseSerializer';
+
+// core
+import Object3DSerializer from './core/Object3DSerializer';
+import SceneSerializer from './core/SceneSerializer';
+import MeshSerializer from './core/MeshSerializer';
+import GroupSerializer from './core/GroupSerializer';
+import BoneSerializer from './core/BoneSerializer';
+import SpriteSerializer from './core/SpriteSerializer';
+import ServerObject from './core/ServerObject';
+import WebGLRendererSerializer from './core/WebGLRendererSerializer';
+
+// app
+import OptionsSerializer from './app/OptionsSerializer';
+import ScriptSerializer from './app/ScriptSerializer';
+import AnimationSerializer from './app/AnimationSerializer';
+
+// camera
+import CamerasSerializer from './camera/CamerasSerializer';
+
+// light
+import AmbientLightSerializer from './light/AmbientLightSerializer';
+import DirectionalLightSerializer from './light/DirectionalLightSerializer';
+import HemisphereLightSerializer from './light/HemisphereLightSerializer';
+import PointLightSerializer from './light/PointLightSerializer';
+import SpotLightSerializer from './light/SpotLightSerializer';
+import RectAreaLightSerializer from './light/RectAreaLightSerializer';
+
+// audio
+import AudioSerializer from './audio/AudioSerializer';
+import AudioListenerSerializer from './audio/AudioListenerSerializer';
+
+// objects
+import ReflectorSerializer from './objects/ReflectorSerializer';
+import FireSerializer from './objects/FireSerializer';
+import SmokeSerializer from './objects/SmokeSerializer';
+import SkySerializer from './objects/SkySerializer';
+import ParticleEmitterSerializer from './objects/ParticleEmitterSerializer';
+import PerlinTerrainSerializer from './objects/PerlinTerrainSerializer';
+
+/**
+ * 场景序列化/反序列化类
+ * @author tengge / https://github.com/tengge1
+ */
+function Converter() {
+ BaseSerializer.call(this);
+}
+
+Converter.prototype = Object.create(BaseSerializer.prototype);
+Converter.prototype.constructor = Converter;
+
+/**
+ * 将应用转为json
+ * @param {*} obj 格式:{ options: options, camera: camera, renderer: renderer, scripts: scripts, scene: scene }
+ */
+Converter.prototype.toJSON = function (obj) {
+ var options = obj.options;
+ var camera = obj.camera;
+ var renderer = obj.renderer;
+ var scripts = obj.scripts;
+ var animation = obj.animation;
+ var scene = obj.scene;
+
+ var list = [];
+
+ // 选项
+ var configJson = (new OptionsSerializer()).toJSON(options);
+ list.push(configJson);
+
+ // 相机
+ var cameraJson = (new CamerasSerializer()).toJSON(camera);
+ list.push(cameraJson);
+
+ // 渲染器
+ var rendererJson = (new WebGLRendererSerializer()).toJSON(renderer);
+ list.push(rendererJson);
+
+ // 脚本
+ var scriptsJson = (new ScriptSerializer()).toJSON(scripts);
+ scriptsJson.forEach(n => {
+ list.push(n);
+ });
+
+ // 动画
+ var animationJson = (new AnimationSerializer()).toJSON(animation);
+ animationJson.forEach(n => {
+ list.push(n);
+ });
+
+ // 音频监听器
+ var audioListener = camera.children.filter(n => n instanceof THREE.AudioListener)[0];
+ if (audioListener) {
+ var audioListenerJson = (new AudioListenerSerializer()).toJSON(audioListener);
+ list.push(audioListenerJson);
+ }
+
+ // 场景
+ this.sceneToJson(scene, list);
+
+ return list;
+};
+
+/**
+ * 场景转json
+ * @param {*} scene 场景
+ * @param {*} list 用于保存json的空数组
+ */
+Converter.prototype.sceneToJson = function (scene, list) {
+ (function serializer(obj) {
+ var json = null;
+
+ if (obj.userData.Server === true) { // 服务器对象
+ json = (new ServerObject()).toJSON(obj);
+ } else if (obj.userData.type === 'Sky') {
+ json = (new SkySerializer()).toJSON(obj);
+ } else if (obj.userData.type === 'Fire') { // 火焰
+ json = (new FireSerializer()).toJSON(obj);
+ } else if (obj.userData.type === 'Smoke') { // 烟
+ json = (new SmokeSerializer()).toJSON(obj);
+ } else if (obj.userData.type === 'ParticleEmitter') { // 粒子发射器
+ json = (new ParticleEmitterSerializer()).toJSON(obj);
+ } else if (obj.userData.type === 'PerlinTerrain') {
+ json = (new PerlinTerrainSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Scene) {
+ json = (new SceneSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Group) {
+ json = (new GroupSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Reflector) {
+ json = (new ReflectorSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Mesh) {
+ json = (new MeshSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Sprite) {
+ json = (new SpriteSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.AmbientLight) {
+ json = (new AmbientLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.DirectionalLight) {
+ json = (new DirectionalLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.HemisphereLight) {
+ json = (new HemisphereLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.PointLight) {
+ json = (new PointLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.RectAreaLight) {
+ json = (new RectAreaLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.SpotLight) {
+ json = (new SpotLightSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Audio) {
+ json = (new AudioSerializer()).toJSON(obj);
+ } else if (obj instanceof THREE.Bone) {
+ json = (new BoneSerializer()).toJSON(obj);
+ }
+
+ if (json) {
+ list.push(json);
+ } else {
+ console.warn(`Converter: 没有 ${obj.constructor.name} 的序列化器。`);
+ }
+
+ // 如果obj.userData.type不为空,则为内置类型,其子项不应该序列化
+ if (obj.children && obj.userData.type === undefined) {
+ obj.children.forEach(n => {
+ serializer.call(this, n);
+ });
+ }
+ })(scene);
+};
+
+/**
+ * 场景反序列化
+ * @param {*} jsons json对象(列表)
+ * @param {*} options 配置选项 格式:{ server: serverUrl } 其中,serverUrl为服务端地址,用于下载模型、纹理等资源
+ */
+Converter.prototype.fromJson = function (jsons, options) {
+ var obj = {
+ options: null,
+ camera: null,
+ renderer: null,
+ scripts: null,
+ animation: null,
+ scene: null
+ };
+
+ // 选项
+ var optionsJson = jsons.filter(n => n.metadata && n.metadata.generator === 'OptionsSerializer')[0];
+ if (optionsJson) {
+ obj.options = (new OptionsSerializer()).fromJSON(optionsJson);
+ } else {
+ console.warn(`Converter: 场景中不存在配置信息。`);
+ }
+
+ // 相机
+ var cameraJson = jsons.filter(n => n.metadata && n.metadata.generator.indexOf('CameraSerializer') > -1)[0];
+ if (cameraJson) {
+ obj.camera = (new CamerasSerializer()).fromJSON(cameraJson);
+ } else {
+ console.warn(`Converter: 场景中不存在相机信息。`);
+ }
+
+ if (options.camera === undefined) {
+ options.camera = obj.camera;
+ }
+
+ // 渲染器
+ var rendererJson = jsons.filter(n => n.metadata && n.metadata.generator.indexOf('WebGLRendererSerializer') > -1)[0];
+ if (rendererJson) {
+ obj.renderer = (new WebGLRendererSerializer()).fromJSON(rendererJson);
+ } else {
+ console.warn(`Converter: 场景中不存在渲染器信息。`);
+ }
+
+ if (options.renderer === undefined) {
+ options.renderer = obj.renderer;
+ }
+
+ // 脚本
+ var scriptJsons = jsons.filter(n => n.metadata && n.metadata.generator === 'ScriptSerializer');
+ if (scriptJsons) {
+ obj.scripts = (new ScriptSerializer()).fromJSON(scriptJsons);
+ }
+
+ // 动画
+ var animationJsons = jsons.filter(n => n.metadata && n.metadata.generator === 'AnimationSerializer');
+ if (animationJsons) {
+ obj.animation = (new AnimationSerializer()).fromJSON(animationJsons);
+ }
+
+ // 音频监听器
+ var audioListenerJson = jsons.filter(n => n.metadata && n.metadata.generator === 'AudioListenerSerializer')[0];
+ var audioListener;
+ if (audioListenerJson) {
+ audioListener = (new AudioListenerSerializer()).fromJSON(audioListenerJson);
+ } else {
+ console.warn(`Converter: 场景种不存在音频监听器信息。`);
+ audioListener = new THREE.AudioListener();
+ }
+ obj.audioListener = audioListener;
+ options.audioListener = audioListener;
+ obj.camera.add(audioListener);
+
+ // 场景
+ return new Promise(resolve => {
+ this.sceneFromJson(jsons, options, audioListener, obj.camera, obj.renderer).then(scene => {
+ obj.scene = scene;
+ resolve(obj);
+ });
+ });
+};
+
+/**
+ * json转场景
+ * @param {*} jsons 反序列化对象列表
+ * @param {*} options 配置信息
+ */
+Converter.prototype.sceneFromJson = function (jsons, options) {
+ var sceneJson = jsons.filter(n => n.metadata && n.metadata.generator === 'SceneSerializer')[0];
+ if (sceneJson === undefined) {
+ console.warn(`Converter: 数据中不存在场景信息。`);
+ return new Promise(resolve => {
+ resolve(new THREE.Scene());
+ });
+ }
+
+ var scene = (new SceneSerializer()).fromJSON(sceneJson);
+
+ var serverObjects = [];
+
+ (function parseJson(json, parent, list) {
+ json.children.forEach(n => {
+ var objJson = list.filter(o => o.uuid === n)[0];
+ if (objJson == null) {
+ console.warn(`Converter: 数据中不存在uuid为${n}的对象数据。`);
+ return;
+ }
+
+ if (objJson.userData && objJson.userData.Server === true) { // 服务端对象
+ serverObjects.push({
+ parent: parent,
+ json: objJson
+ });
+ return;
+ }
+
+ var obj = null;
+
+ switch (objJson.metadata.generator) {
+ case 'SceneSerializer':
+ obj = (new SceneSerializer()).fromJSON(objJson);
+ break;
+ case 'GroupSerializer':
+ obj = (new GroupSerializer()).fromJSON(objJson);
+ break;
+ case 'ReflectorSerializer':
+ obj = (new ReflectorSerializer()).fromJSON(objJson);
+ break;
+ case 'MeshSerializer':
+ obj = (new MeshSerializer()).fromJSON(objJson);
+ break;
+ case 'SpriteSerializer':
+ obj = (new SpriteSerializer()).fromJSON(objJson);
+ break;
+ case 'AmbientLightSerializer':
+ obj = (new AmbientLightSerializer()).fromJSON(objJson);
+ break;
+ case 'DirectionalLightSerializer':
+ obj = (new DirectionalLightSerializer()).fromJSON(objJson);
+ break;
+ case 'HemisphereLightSerializer':
+ obj = (new HemisphereLightSerializer()).fromJSON(objJson);
+ break;
+ case 'PointLightSerializer':
+ obj = (new PointLightSerializer()).fromJSON(objJson);
+ break;
+ case 'RectAreaLightSerializer':
+ obj = (new RectAreaLightSerializer()).fromJSON(objJson);
+ break;
+ case 'SpotLightSerializer':
+ obj = (new SpotLightSerializer()).fromJSON(objJson);
+ break;
+ case 'AudioSerializer':
+ obj = (new AudioSerializer()).fromJSON(objJson, undefined, options.audioListener);
+ break;
+ case 'FireSerializer':
+ obj = (new FireSerializer()).fromJSON(objJson, undefined, options.camera);
+ break;
+ case 'SmokeSerializer':
+ obj = (new SmokeSerializer()).fromJSON(objJson, undefined, options.camera, options.renderer);
+ break;
+ case 'BoneSerializer':
+ obj = (new BoneSerializer()).fromJSON(objJson);
+ break;
+ case 'SkySerializer':
+ obj = (new SkySerializer()).fromJSON(objJson);
+ break;
+ case 'ParticleEmitterSerializer':
+ obj = (new ParticleEmitterSerializer()).fromJSON(objJson);
+ break;
+ case 'PerlinTerrainSerializer':
+ obj = (new PerlinTerrainSerializer()).fromJSON(objJson);
+ break;
+ }
+
+ if (obj) {
+ parent.add(obj);
+ } else {
+ console.warn(`Converter: 不存在${objJson.metadata.type}的反序列化器。`);
+ }
+
+ // objJson.userData.type不为空,说明它是内置类型,其子项不应该被反序列化
+ if (objJson.userData.type === undefined && objJson.children && Array.isArray(objJson.children) && objJson.children.length > 0 && obj) {
+ parseJson(objJson, obj, list);
+ }
+ });
+ })(sceneJson, scene, jsons);
+
+ if (serverObjects.length === 0) {
+ return new Promise(resolve => {
+ resolve(scene);
+ });
+ }
+
+ var promises = serverObjects.map(serverObj => {
+ return new Promise(resolve => {
+ (new ServerObject()).fromJSON(serverObj.json, options).then(obj => {
+ if (obj) {
+ serverObj.parent.add(obj);
+ } else {
+ console.warn(`Converter: 服务器资源${serverObj.json.uuid}下载失败。`);
+ }
+ resolve();
+ });
+ });
+ })
+
+ return new Promise(resolve => {
+ Promise.all(promises).then(() => {
+ resolve(scene);
+ });
+ });
+};
+
+export default Converter;
diff --git a/ShadowEditor.Core/src/serialization/Metadata.js b/ShadowEditor.Core/src/serialization/Metadata.js
new file mode 100644
index 00000000..760b9580
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/Metadata.js
@@ -0,0 +1,11 @@
+/**
+ * 场景序列化信息
+ * @author tengge / https://github.com/tengge1
+ */
+var Metadata = {
+ generator: 'ShadowEditor',
+ type: 'Object',
+ version: '0.0.1'
+};
+
+export default Metadata;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/app/AnimationSerializer.js b/ShadowEditor.Core/src/serialization/app/AnimationSerializer.js
new file mode 100644
index 00000000..cba4375d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/app/AnimationSerializer.js
@@ -0,0 +1,78 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * AnimationSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function AnimationSerializer() {
+ BaseSerializer.call(this);
+}
+
+AnimationSerializer.prototype = Object.create(BaseSerializer.prototype);
+AnimationSerializer.prototype.constructor = AnimationSerializer;
+
+AnimationSerializer.prototype.toJSON = function (manager) {
+ var list = [];
+
+ var groups = manager.animations;
+
+ groups.forEach(n => {
+ var json = BaseSerializer.prototype.toJSON.call(this, n);
+
+ Object.assign(json, n);
+ json.animations = n.animations.map(m => m.uuid);
+
+ list.push(json);
+
+ n.animations.forEach(m => {
+ var json1 = BaseSerializer.prototype.toJSON.call(this, m);
+
+ Object.assign(json1, m);
+
+ list.push(json1);
+ });
+ });
+
+ return list;
+};
+
+AnimationSerializer.prototype.fromJSON = function (jsons) {
+ var list = [];
+
+ var groups = jsons.filter(n => n.type === 'AnimationGroup');
+
+ groups.forEach(n => {
+ var obj = BaseSerializer.prototype.fromJSON.call(this, n);
+ Object.assign(obj, n);
+
+ obj.id = obj._id;
+ delete obj._id;
+ delete obj.metadata;
+
+ var animations = obj.animations;
+ obj.animations = [];
+
+ animations.forEach(m => {
+ var json1 = jsons.filter(o => o.uuid === m)[0];
+
+ if (json1 === undefined) {
+ console.warn(`AnimationSerializer: 不存在uuid为${m}的动画。`);
+ return;
+ }
+ var obj1 = BaseSerializer.prototype.fromJSON.call(this, json1);
+
+ Object.assign(obj1, json1);
+ obj1.id = obj1._id;
+ delete obj1._id;
+ delete obj1.metadata;
+
+ obj.animations.push(obj1);
+ });
+
+ list.push(obj);
+ });
+
+ return list;
+};
+
+export default AnimationSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/app/OptionsSerializer.js b/ShadowEditor.Core/src/serialization/app/OptionsSerializer.js
new file mode 100644
index 00000000..e0aa7932
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/app/OptionsSerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * OptionsSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function OptionsSerializer() {
+ BaseSerializer.call(this);
+}
+
+OptionsSerializer.prototype = Object.create(BaseSerializer.prototype);
+OptionsSerializer.prototype.constructor = OptionsSerializer;
+
+OptionsSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+ Object.assign(json, obj);
+ return json;
+};
+
+OptionsSerializer.prototype.fromJSON = function (json) {
+ var obj = {};
+
+ Object.keys(json).forEach(n => {
+ if (n === '_id' || n === 'metadata' || n === 'server') { // 由于不同服务器的服务端不一样,所以不能反序列化server配置
+ return;
+ }
+ obj[n] = json[n];
+ });
+
+ return obj;
+};
+
+export default OptionsSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/app/ScriptSerializer.js b/ShadowEditor.Core/src/serialization/app/ScriptSerializer.js
new file mode 100644
index 00000000..bc2ef50a
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/app/ScriptSerializer.js
@@ -0,0 +1,52 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * ScriptSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ScriptSerializer() {
+ BaseSerializer.call(this);
+}
+
+ScriptSerializer.prototype = Object.create(BaseSerializer.prototype);
+ScriptSerializer.prototype.constructor = ScriptSerializer;
+
+ScriptSerializer.prototype.toJSON = function (scripts) {
+ var list = [];
+
+ Object.keys(scripts).forEach(uuid => {
+ var json = BaseSerializer.prototype.toJSON.call(this);
+
+ var script = scripts[uuid];
+
+ Object.assign(json, {
+ id: script.id,
+ name: script.name,
+ type: script.type,
+ source: script.source,
+ uuid: script.uuid
+ });
+
+ list.push(json);
+ });
+
+ return list;
+};
+
+ScriptSerializer.prototype.fromJSON = function (jsons, parent) {
+ parent = parent || {};
+
+ jsons.forEach(json => {
+ parent[json.uuid] = {
+ id: json.id,
+ name: json.name,
+ type: json.type,
+ source: json.source,
+ uuid: json.uuid
+ };
+ });
+
+ return parent;
+};
+
+export default ScriptSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/audio/AudioListenerSerializer.js b/ShadowEditor.Core/src/serialization/audio/AudioListenerSerializer.js
new file mode 100644
index 00000000..f2764c5d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/audio/AudioListenerSerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+
+/**
+ * AudioListenerSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function AudioListenerSerializer() {
+ BaseSerializer.call(this);
+}
+
+AudioListenerSerializer.prototype = Object.create(BaseSerializer.prototype);
+AudioListenerSerializer.prototype.constructor = AudioListenerSerializer;
+
+AudioListenerSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.masterVolume = obj.getMasterVolume();
+
+ return json;
+};
+
+AudioListenerSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.AudioListener() : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.setMasterVolume(json.masterVolume);
+
+ return obj;
+};
+
+export default AudioListenerSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/audio/AudioSerializer.js b/ShadowEditor.Core/src/serialization/audio/AudioSerializer.js
new file mode 100644
index 00000000..141a1472
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/audio/AudioSerializer.js
@@ -0,0 +1,40 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+
+/**
+ * AudioSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function AudioSerializer() {
+ BaseSerializer.call(this);
+}
+
+AudioSerializer.prototype = Object.create(BaseSerializer.prototype);
+AudioSerializer.prototype.constructor = AudioSerializer;
+
+AudioSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.autoplay = obj.autoplay;
+ json.loop = obj.getLoop();
+ json.volume = obj.getVolume();
+
+ return json;
+};
+
+AudioSerializer.prototype.fromJSON = function (json, parent, audioListener) {
+ if (audioListener === undefined) {
+ audioListener = new THREE.AudioListener();
+ }
+ var obj = parent === undefined ? new THREE.Audio(audioListener) : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.autoplay = json.autoplay;
+ obj.setLoop(json.loop);
+ obj.setVolume(json.volume);
+
+ return obj;
+};
+
+export default AudioSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/camera/CameraSerializer.js b/ShadowEditor.Core/src/serialization/camera/CameraSerializer.js
new file mode 100644
index 00000000..4db588c8
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/camera/CameraSerializer.js
@@ -0,0 +1,45 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+
+/**
+ * CameraSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CameraSerializer() {
+ BaseSerializer.call(this);
+}
+
+CameraSerializer.prototype = Object.create(BaseSerializer.prototype);
+CameraSerializer.prototype.constructor = CameraSerializer;
+
+CameraSerializer.prototype.filter = function (obj) {
+ if (obj instanceof THREE.Camera) {
+ return true;
+ } else if (obj.metadata && obj.metadata.generator === this.constructor.name) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+CameraSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.matrixWorldInverse = obj.matrixWorldInverse;
+ json.projectionMatrix = obj.projectionMatrix;
+
+ return json;
+};
+
+CameraSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Camera() : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.matrixWorldInverse.copy(json.matrixWorldInverse);
+ obj.projectionMatrix.copy(json.projectionMatrix);
+
+ return obj;
+};
+
+export default CameraSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/camera/CamerasSerializer.js b/ShadowEditor.Core/src/serialization/camera/CamerasSerializer.js
new file mode 100644
index 00000000..895166ff
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/camera/CamerasSerializer.js
@@ -0,0 +1,48 @@
+import BaseSerializer from '../BaseSerializer';
+
+import CameraSerializer from './CameraSerializer';
+import OrthographicCameraSerializer from './OrthographicCameraSerializer';
+import PerspectiveCameraSerializer from './PerspectiveCameraSerializer';
+
+var Serializers = {
+ 'OrthographicCamera': OrthographicCameraSerializer,
+ 'PerspectiveCamera': PerspectiveCameraSerializer,
+ 'Camera': CameraSerializer
+};
+
+/**
+ * CamerasSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CamerasSerializer() {
+ BaseSerializer.call(this);
+}
+
+CamerasSerializer.prototype = Object.create(BaseSerializer.prototype);
+CamerasSerializer.prototype.constructor = CamerasSerializer;
+
+CamerasSerializer.prototype.toJSON = function (obj) {
+ var serializer = Serializers[obj.constructor.name];
+
+ if (serializer === undefined) {
+ console.warn(`CamerasSerializer: 无法序列化${obj.constructor.name}。`);
+ return null;
+ }
+
+ return (new serializer()).toJSON(obj);
+};
+
+CamerasSerializer.prototype.fromJSON = function (json, parent) {
+ var generator = json.metadata.generator;
+
+ var serializer = Serializers[generator.replace('Serializer', '')];
+
+ if (serializer === undefined) {
+ console.warn(`CamerasSerializer: 不存在 ${generator} 的反序列化器`);
+ return null;
+ }
+
+ return (new serializer()).fromJSON(json, parent);
+};
+
+export default CamerasSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/camera/OrthographicCameraSerializer.js b/ShadowEditor.Core/src/serialization/camera/OrthographicCameraSerializer.js
new file mode 100644
index 00000000..a9d8afab
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/camera/OrthographicCameraSerializer.js
@@ -0,0 +1,47 @@
+import BaseSerializer from '../BaseSerializer';
+import CameraSerializer from './CameraSerializer';
+
+/**
+ * OrthographicCameraSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function OrthographicCameraSerializer() {
+ BaseSerializer.call(this);
+}
+
+OrthographicCameraSerializer.prototype = Object.create(BaseSerializer.prototype);
+OrthographicCameraSerializer.prototype.constructor = OrthographicCameraSerializer;
+
+OrthographicCameraSerializer.prototype.toJSON = function (obj) {
+ var json = CameraSerializer.prototype.toJSON.call(this, obj);
+
+ json.bottom = obj.bottom;
+ json.far = obj.far;
+ json.left = obj.left;
+ json.near = obj.near;
+ json.right = obj.right;
+ json.top = obj.top;
+ json.view = obj.view;
+ json.zoom = obj.zoom;
+
+ return json;
+};
+
+OrthographicCameraSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.OrthographicCamera() : parent;
+
+ CameraSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.bottom = json.bottom;
+ obj.far = json.far;
+ obj.left = json.left;
+ obj.near = json.near;
+ obj.right = json.right;
+ obj.top = json.top;
+ obj.view = json.view;
+ obj.zoom = json.zoom;
+
+ return obj;
+};
+
+export default OrthographicCameraSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/camera/PerspectiveCameraSerializer.js b/ShadowEditor.Core/src/serialization/camera/PerspectiveCameraSerializer.js
new file mode 100644
index 00000000..04e755a5
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/camera/PerspectiveCameraSerializer.js
@@ -0,0 +1,49 @@
+import BaseSerializer from '../BaseSerializer';
+import CameraSerializer from './CameraSerializer';
+
+/**
+ * PerspectiveCameraSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PerspectiveCameraSerializer() {
+ BaseSerializer.call(this);
+}
+
+PerspectiveCameraSerializer.prototype = Object.create(BaseSerializer.prototype);
+PerspectiveCameraSerializer.prototype.constructor = PerspectiveCameraSerializer;
+
+PerspectiveCameraSerializer.prototype.toJSON = function (obj) {
+ var json = CameraSerializer.prototype.toJSON.call(this, obj);
+
+ json.aspect = obj.aspect;
+ json.far = obj.far;
+ json.filmGauge = obj.filmGauge;
+ json.filmOffset = obj.filmOffset;
+ json.focus = obj.focus;
+ json.fov = obj.fov;
+ json.near = obj.near;
+ json.view = obj.view;
+ json.zoom = obj.zoom;
+
+ return json;
+};
+
+PerspectiveCameraSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PerspectiveCamera() : parent;
+
+ CameraSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.aspect = json.aspect;
+ obj.far = json.far;
+ obj.filmGauge = json.filmGauge;
+ obj.filmOffset = json.filmOffset;
+ obj.focus = json.focus;
+ obj.fov = json.fov;
+ obj.near = json.near;
+ obj.view = json.view;
+ obj.zoom = json.zoom;
+
+ return obj;
+};
+
+export default PerspectiveCameraSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/BoneSerializer.js b/ShadowEditor.Core/src/serialization/core/BoneSerializer.js
new file mode 100644
index 00000000..c0ac7a35
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/BoneSerializer.js
@@ -0,0 +1,29 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+
+/**
+ * BoneSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function BoneSerializer() {
+ BaseSerializer.call(this);
+}
+
+BoneSerializer.prototype = Object.create(BaseSerializer.prototype);
+BoneSerializer.prototype.constructor = BoneSerializer;
+
+BoneSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ return json;
+};
+
+BoneSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Bone() : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default BoneSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/GroupSerializer.js b/ShadowEditor.Core/src/serialization/core/GroupSerializer.js
new file mode 100644
index 00000000..c046ab0a
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/GroupSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+
+/**
+ * GroupSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function GroupSerializer() {
+ BaseSerializer.call(this);
+}
+
+GroupSerializer.prototype = Object.create(BaseSerializer.prototype);
+GroupSerializer.prototype.constructor = GroupSerializer;
+
+GroupSerializer.prototype.toJSON = function (obj) {
+ return Object3DSerializer.prototype.toJSON.call(this, obj);
+};
+
+GroupSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Group() : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default GroupSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/MeshSerializer.js b/ShadowEditor.Core/src/serialization/core/MeshSerializer.js
new file mode 100644
index 00000000..1d3def30
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/MeshSerializer.js
@@ -0,0 +1,56 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+
+import GeometriesSerializer from '../geometry/GeometriesSerializer';
+import MaterialsSerializer from '../material/MaterialsSerializer';
+
+/**
+ * MeshSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshSerializer.prototype.constructor = MeshSerializer;
+
+MeshSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.drawMode = obj.drawMode;
+ json.geometry = (new GeometriesSerializer()).toJSON(obj.geometry);
+ json.material = (new MaterialsSerializer()).toJSON(obj.material);
+
+ return json;
+};
+
+MeshSerializer.prototype.fromJSON = function (json, parent) {
+ // 子类创建模型
+ if (parent !== undefined) {
+ var obj = parent;
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+ return obj;
+ }
+
+ // 其他模型
+ if (json.geometry == null) {
+ console.warn(`MeshSerializer: ${json.name} json.geometry未定义。`);
+ return null;
+ }
+ if (json.material == null) {
+ console.warn(`MeshSerializer: ${json.name} json.material未定义。`);
+ return null;
+ }
+
+ var geometry = (new GeometriesSerializer()).fromJSON(json.geometry);
+ var material = (new MaterialsSerializer()).fromJSON(json.material);
+
+ var obj = new THREE.Mesh(geometry, material);
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/Object3DSerializer.js b/ShadowEditor.Core/src/serialization/core/Object3DSerializer.js
new file mode 100644
index 00000000..64935ba9
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/Object3DSerializer.js
@@ -0,0 +1,86 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * Object3DSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function Object3DSerializer() {
+ BaseSerializer.call(this);
+}
+
+Object3DSerializer.prototype = Object.create(BaseSerializer.prototype);
+Object3DSerializer.prototype.constructor = Object3DSerializer;
+
+Object3DSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.castShadow = obj.castShadow;
+ json.children = obj.children.map(child => {
+ return child.uuid;
+ });
+ json.frustumCulled = obj.frustumCulled;
+ json.layers = obj.layers;
+ json.matrix = obj.matrix;
+ json.matrixAutoUpdate = obj.matrixAutoUpdate;
+ json.matrixWorld = obj.matrixWorld;
+ json.matrixWorldNeedsUpdate = obj.matrixWorldNeedsUpdate;
+ json.modelViewMatrix = obj.modelViewMatrix;
+ json.name = obj.name;
+ json.normalMatrix = obj.normalMatrix;
+ json.parent = obj.parent == null ? null : obj.parent.uuid;
+ json.position = obj.position;
+ json.quaternion = {
+ x: obj.quaternion.x,
+ y: obj.quaternion.y,
+ z: obj.quaternion.z,
+ w: obj.quaternion.w
+ };
+ json.receiveShadow = obj.receiveShadow;
+ json.renderOrder = obj.renderOrder;
+ json.rotation = {
+ x: obj.rotation.x,
+ y: obj.rotation.y,
+ z: obj.rotation.z,
+ order: obj.rotation.order
+ };
+ json.scale = obj.scale;
+ json.type = obj.type;
+ json.up = obj.up;
+
+ json.userData = {};
+ Object.assign(json.userData, obj.userData);
+
+ json.uuid = obj.uuid;
+ json.visible = obj.visible;
+ json.isObject3D = obj.isObject3D;
+
+ return json;
+};
+
+Object3DSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? THREE.Object3D : parent;
+
+ BaseSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.castShadow = json.castShadow;
+ obj.frustumCulled = json.frustumCulled;
+ obj.type = json.type;
+ obj.uuid = json.uuid;
+
+ obj.matrix.copy(json.matrix);
+ obj.matrixAutoUpdate = json.matrixAutoUpdate;
+ obj.name = json.name;
+ obj.position.copy(json.position);
+ obj.quaternion.copy(json.quaternion);
+ obj.receiveShadow = json.receiveShadow;
+ obj.renderOrder = json.renderOrder;
+ obj.rotation.set(json.rotation.x, json.rotation.y, json.rotation.z, json.rotation.order);
+ obj.scale.copy(json.scale);
+ obj.up.copy(json.up);
+ obj.visible = json.visible;
+ Object.assign(obj.userData, json.userData);
+
+ return obj;
+};
+
+export default Object3DSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/SceneSerializer.js b/ShadowEditor.Core/src/serialization/core/SceneSerializer.js
new file mode 100644
index 00000000..feb8423e
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/SceneSerializer.js
@@ -0,0 +1,59 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+import MaterialsSerializer from '../material/MaterialsSerializer';
+import TexturesSerializer from '../texture/TexturesSerializer';
+
+/**
+ * SceneSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SceneSerializer() {
+ BaseSerializer.call(this);
+}
+
+SceneSerializer.prototype = Object.create(BaseSerializer.prototype);
+SceneSerializer.prototype.constructor = SceneSerializer;
+
+SceneSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ if (obj.background instanceof THREE.Texture) { // 天空盒和背景图片
+ json.background = new TexturesSerializer().toJSON(obj.background);
+ } else { // 纯色
+ json.background = obj.background;
+ }
+
+ json.fog = obj.fog;
+ json.overrideMaterial = obj.overrideMaterial == null ? null : (new MaterialsSerializer()).toJSON(obj.overrideMaterial);
+
+ return json;
+};
+
+SceneSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Scene() : parent;
+
+ Object3DSerializer.prototype.fromJSON(json, obj);
+
+ if (json.background.metadata &&
+ (json.background.metadata.generator === 'CubeTextureSerializer' ||
+ json.background.metadata.generator === 'TextureSerializer')
+ ) { // 天空盒和背景图片
+ obj.background = new TexturesSerializer().fromJSON(json.background);
+ } else { // 纯色
+ obj.background = new THREE.Color(json.background);
+ }
+
+ if (json.fog && (json.fog.type === 'Fog' || json.fog instanceof THREE.Fog)) {
+ obj.fog = new THREE.Fog(json.fog.color, json.fog.near, json.fog.far);
+ } else if (json.fog && (json.fog.type === 'FogExp2' || json.fog instanceof THREE.FogExp2)) {
+ obj.fog = new THREE.FogExp2(json.fog.color, json.fog.density);
+ } else if (json.fog) {
+ console.warn(`SceneSerializer: unknown fog type ${json.fog.type}.`);
+ }
+
+ obj.overrideMaterial = json.overrideMaterial == null ? null : (new MaterialsSerializer()).fromJSON(json.overrideMaterial);
+
+ return obj;
+};
+
+export default SceneSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/ServerObject.js b/ShadowEditor.Core/src/serialization/core/ServerObject.js
new file mode 100644
index 00000000..7191a50b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/ServerObject.js
@@ -0,0 +1,45 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+import ModelLoader from '../../loader/ModelLoader';
+
+/**
+ * ServerObject
+ * @author tengge / https://github.com/tengge1
+ */
+function ServerObject() {
+ BaseSerializer.call(this);
+}
+
+ServerObject.prototype = Object.create(BaseSerializer.prototype);
+ServerObject.prototype.constructor = ServerObject;
+
+ServerObject.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+ json.userData = Object.assign({}, obj.userData);
+ delete json.userData.model;
+ return json;
+};
+
+ServerObject.prototype.fromJSON = function (json, options) {
+ var url = json.userData.Url;
+
+ if (url.indexOf(';') > -1) { // 包含多个入口文件
+ url = url.split(';').map(n => options.server + n);
+ } else {
+ url = options.server + url;
+ }
+
+ return new Promise(resolve => {
+ var loader = new ModelLoader();
+ loader.load(url, json.userData).then(obj => {
+ if (obj) {
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+ resolve(obj);
+ } else {
+ resolve(null);
+ }
+ });
+ });
+};
+
+export default ServerObject;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/SpriteSerializer.js b/ShadowEditor.Core/src/serialization/core/SpriteSerializer.js
new file mode 100644
index 00000000..25ed21b7
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/SpriteSerializer.js
@@ -0,0 +1,50 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from './Object3DSerializer';
+
+import GeometriesSerializer from '../geometry/GeometriesSerializer';
+import MaterialsSerializer from '../material/MaterialsSerializer';
+
+/**
+ * SpriteSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SpriteSerializer() {
+ BaseSerializer.call(this);
+}
+
+SpriteSerializer.prototype = Object.create(BaseSerializer.prototype);
+SpriteSerializer.prototype.constructor = SpriteSerializer;
+
+SpriteSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.center = obj.center;
+ json.material = (new MaterialsSerializer()).toJSON(obj.material);
+ json.z = obj.z;
+ json.isSprite = obj.isSprite;
+
+ return json;
+};
+
+SpriteSerializer.prototype.fromJSON = function (json, parent) {
+ var material;
+
+ if (parent === undefined) {
+ if (json.material == null) {
+ console.warn(`SpriteSerializer: ${json.name} json.material未定义。`);
+ return null;
+ }
+ material = (new MaterialsSerializer()).fromJSON(json.material);
+ }
+
+ var obj = parent === undefined ? new THREE.Sprite(material) : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.center.copy(json.center);
+ obj.z = json.z;
+
+ return obj;
+};
+
+export default SpriteSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/WebGLRenderTargetSerializer.js b/ShadowEditor.Core/src/serialization/core/WebGLRenderTargetSerializer.js
new file mode 100644
index 00000000..3688f987
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/WebGLRenderTargetSerializer.js
@@ -0,0 +1,49 @@
+import BaseSerializer from '../BaseSerializer';
+import TexturesSerializer from '../texture/TexturesSerializer';
+
+/**
+ * WebGLRenderTargetSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function WebGLRenderTargetSerializer() {
+ BaseSerializer.call(this);
+}
+
+WebGLRenderTargetSerializer.prototype = Object.create(BaseSerializer.prototype);
+WebGLRenderTargetSerializer.prototype.constructor = WebGLRenderTargetSerializer;
+
+WebGLRenderTargetSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.depthBuffer = obj.depthBuffer;
+ json.depthTexture = obj.depthTexture == null ? null : (new TexturesSerializer()).toJSON(obj.depthTexture);
+ json.height = obj.height;
+ json.scissor = obj.scissor;
+ json.scissorTest = obj.scissorTest;
+ json.stencilBuffer = obj.stencilBuffer;
+ json.texture = obj.texture == null ? null : (new TexturesSerializer()).toJSON(obj.texture);
+ json.viewport = obj.viewport;
+ json.width = obj.width;
+ json.isWebGLRenderTarget = obj.isWebGLRenderTarget;
+
+ return json;
+};
+
+WebGLRenderTargetSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.WebGLRenderTarget(json.width, json.height) : parent;
+
+ obj.depthBuffer = json.depthBuffer;
+ obj.depthTexture = json.depthTexture == null ? null : (new TexturesSerializer()).fromJSON(json.depthTexture);
+ obj.height = json.height;
+ obj.scissor.copy(json.scissor);
+ obj.scissorTest = json.scissorTest;
+ obj.stencilBuffer = json.stencilBuffer;
+ obj.texture = json.texture == null ? null : (new TexturesSerializer()).fromJSON(json.texture);
+ obj.viewport.copy(json.viewport);
+ obj.width = json.width;
+ obj.isWebGLRenderTarget = json.isWebGLRenderTarget;
+
+ return obj;
+};
+
+export default WebGLRenderTargetSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/WebGLRendererSerializer.js b/ShadowEditor.Core/src/serialization/core/WebGLRendererSerializer.js
new file mode 100644
index 00000000..af7622b3
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/WebGLRendererSerializer.js
@@ -0,0 +1,62 @@
+import BaseSerializer from '../BaseSerializer';
+import WebGLShadowMapSerializer from './WebGLShadowMapSerializer';
+
+/**
+ * WebGLRendererSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function WebGLRendererSerializer() {
+ BaseSerializer.call(this);
+}
+
+WebGLRendererSerializer.prototype = Object.create(BaseSerializer.prototype);
+WebGLRendererSerializer.prototype.constructor = WebGLRendererSerializer;
+
+WebGLRendererSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.antialias = true;
+ json.autoClear = obj.autoClear;
+ json.autoClearColor = obj.autoClearColor;
+ json.autoClearDepth = obj.autoClearDepth;
+ json.autoClearStencil = obj.autoClearStencil;
+ json.autoUpdateScene = obj.autoUpdateScene;
+ json.clippingPlanes = obj.clippingPlanes;
+ json.gammaFactor = obj.gammaFactor;
+ json.gammaInput = obj.gammaInput;
+ json.gammaOutput = obj.gammaOutput;
+ json.localClippingEnabled = obj.localClippingEnabled;
+ json.physicallyCorrectLights = obj.physicallyCorrectLights;
+ json.shadowMap = (new WebGLShadowMapSerializer()).toJSON(obj.shadowMap);
+ json.sortObjects = obj.sortObjects;
+ json.toneMapping = obj.toneMapping;
+ json.toneMappingExposure = obj.toneMappingExposure;
+ json.toneMappingWhitePoint = obj.toneMappingWhitePoint;
+
+ return json;
+};
+
+WebGLRendererSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.WebGLRenderer({ antialias: json.antialias }) : parent;
+
+ obj.autoClear = json.autoClear;
+ obj.autoClearColor = json.autoClearColor;
+ obj.autoClearDepth = json.autoClearDepth;
+ obj.autoClearStencil = json.autoClearStencil;
+ obj.autoUpdateScene = json.autoUpdateScene;
+ obj.clippingPlanes = json.clippingPlanes;
+ obj.gammaFactor = json.gammaFactor;
+ obj.gammaInput = json.gammaInput;
+ obj.gammaOutput = json.gammaOutput;
+ obj.localClippingEnabled = json.localClippingEnabled;
+ obj.physicallyCorrectLights = json.physicallyCorrectLights;
+ (new WebGLShadowMapSerializer()).fromJSON(json.shadowMap, obj.shadowMap);
+ obj.sortObjects = json.sortObjects;
+ obj.toneMapping = json.toneMapping;
+ obj.toneMappingExposure = json.toneMappingExposure;
+ obj.toneMappingWhitePoint = json.toneMappingWhitePoint;
+
+ return obj;
+};
+
+export default WebGLRendererSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/core/WebGLShadowMapSerializer.js b/ShadowEditor.Core/src/serialization/core/WebGLShadowMapSerializer.js
new file mode 100644
index 00000000..2cad8686
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/core/WebGLShadowMapSerializer.js
@@ -0,0 +1,42 @@
+import BaseSerializer from '../BaseSerializer';
+import TexturesSerializer from '../texture/TexturesSerializer';
+
+/**
+ * WebGLShadowMapSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function WebGLShadowMapSerializer() {
+ BaseSerializer.call(this);
+}
+
+WebGLShadowMapSerializer.prototype = Object.create(BaseSerializer.prototype);
+WebGLShadowMapSerializer.prototype.constructor = WebGLShadowMapSerializer;
+
+WebGLShadowMapSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.autoUpdate = obj.autoUpdate;
+ json.enabled = obj.enabled;
+ json.needsUpdate = obj.needsUpdate;
+ json.type = obj.type;
+
+ return json;
+};
+
+WebGLShadowMapSerializer.prototype.fromJSON = function (json, parent) {
+ if (parent === undefined) {
+ console.warn(`WebGLShadowMapSerializer: parent不允许为空!`);
+ return null;
+ }
+
+ var obj = parent;
+
+ obj.autoUpdate = json.autoUpdate;
+ obj.enabled = json.enabled;
+ obj.needsUpdate = true;
+ obj.type = json.type;
+
+ return obj;
+};
+
+export default WebGLShadowMapSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/BoxBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/BoxBufferGeometrySerializer.js
new file mode 100644
index 00000000..da60a542
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/BoxBufferGeometrySerializer.js
@@ -0,0 +1,34 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * BoxBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function BoxBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+BoxBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+BoxBufferGeometrySerializer.prototype.constructor = BoxBufferGeometrySerializer;
+
+BoxBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+BoxBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.BoxBufferGeometry(
+ json.parameters.width,
+ json.parameters.height,
+ json.parameters.depth,
+ json.parameters.widthSegments,
+ json.parameters.heightSegments,
+ json.parameters.depthSegments
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default BoxBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/BufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/BufferGeometrySerializer.js
new file mode 100644
index 00000000..c1129dc4
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/BufferGeometrySerializer.js
@@ -0,0 +1,71 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * BufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function BufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+BufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+BufferGeometrySerializer.prototype.constructor = BufferGeometrySerializer;
+
+BufferGeometrySerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ // json.attributes太大,不要保存在Mongo
+ // json.attributes = obj.attributes;
+ json.boundingBox = obj.boundingBox;
+ json.boundingSphere = obj.boundingSphere;
+ json.drawRange = obj.drawRange;
+ json.groups = obj.groups;
+ // json.index = obj.index;
+ json.morphAttributes = obj.morphAttributes;
+ json.name = obj.name;
+ json.parameters = obj.parameters;
+ json.type = obj.type;
+ json.userData = obj.userData;
+ json.uuid = obj.uuid;
+
+ return json;
+};
+
+BufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.BufferGeometry() : parent;
+
+ BaseSerializer.prototype.fromJSON.call(this, json, obj);
+
+ // obj.attributes = json.attributes;
+ // if (json.boundingBox) {
+ // obj.boundingBox = new THREE.Box3(
+ // new THREE.Vector3().copy(json.boundingBox.min),
+ // new THREE.Vector3().copy(json.boundingBox.max),
+ // );
+ // }
+
+ // if (json.boundingSphere) {
+ // obj.boundingSphere = new THREE.Sphere(
+ // new THREE.Vector3().copy(json.boundingSphere.center),
+ // json.boundingSphere.radius
+ // );
+ // }
+
+ // if (json.drawRange) {
+ // obj.drawRange.start = json.drawRange.start;
+ // obj.drawRange.count = json.drawRange.count === null ? Infinity : json.drawRange.count;
+ // }
+
+ obj.groups = json.groups;
+ // obj.index = json.index;
+ obj.morphAttributes = json.morphAttributes;
+ obj.name = json.name;
+ obj.parameters = json.parameters;
+ obj.type = json.type;
+ obj.userData = json.userData;
+ obj.uuid = json.uuid;
+
+ return obj;
+};
+
+export default BufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/CircleBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/CircleBufferGeometrySerializer.js
new file mode 100644
index 00000000..d651ec7f
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/CircleBufferGeometrySerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * CircleBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CircleBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+CircleBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+CircleBufferGeometrySerializer.prototype.constructor = CircleBufferGeometrySerializer;
+
+CircleBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+CircleBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.CircleBufferGeometry(
+ json.parameters.radius,
+ json.parameters.segments,
+ json.parameters.thetaStart,
+ json.parameters.thetaLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default CircleBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/ConeBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/ConeBufferGeometrySerializer.js
new file mode 100644
index 00000000..b25d5610
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/ConeBufferGeometrySerializer.js
@@ -0,0 +1,35 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * ConeBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ConeBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+ConeBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+ConeBufferGeometrySerializer.prototype.constructor = ConeBufferGeometrySerializer;
+
+ConeBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+ConeBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ConeBufferGeometry(
+ json.parameters.radius,
+ json.parameters.height,
+ json.parameters.radialSegments,
+ json.parameters.heightSegments,
+ json.parameters.openEnded,
+ json.parameters.thetaStart,
+ json.parameters.thetaLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ConeBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/CylinderBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/CylinderBufferGeometrySerializer.js
new file mode 100644
index 00000000..ba70859b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/CylinderBufferGeometrySerializer.js
@@ -0,0 +1,36 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * CylinderBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CylinderBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+CylinderBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+CylinderBufferGeometrySerializer.prototype.constructor = CylinderBufferGeometrySerializer;
+
+CylinderBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+CylinderBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.CylinderBufferGeometry(
+ json.parameters.radiusTop,
+ json.parameters.radiusBottom,
+ json.parameters.height,
+ json.parameters.radialSegments,
+ json.parameters.heightSegments,
+ json.parameters.openEnded,
+ json.parameters.thetaStart,
+ json.parameters.thetaLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default CylinderBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/DodecahedronBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/DodecahedronBufferGeometrySerializer.js
new file mode 100644
index 00000000..b2ac88e9
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/DodecahedronBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * DodecahedronBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function DodecahedronBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+DodecahedronBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+DodecahedronBufferGeometrySerializer.prototype.constructor = DodecahedronBufferGeometrySerializer;
+
+DodecahedronBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+DodecahedronBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.DodecahedronBufferGeometry(
+ json.parameters.radius,
+ json.parameters.detail
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default DodecahedronBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/ExtrudeBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/ExtrudeBufferGeometrySerializer.js
new file mode 100644
index 00000000..95765962
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/ExtrudeBufferGeometrySerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * ExtrudeBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ExtrudeBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+ExtrudeBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+ExtrudeBufferGeometrySerializer.prototype.constructor = ExtrudeBufferGeometrySerializer;
+
+ExtrudeBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+ExtrudeBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ // TODO
+
+ var obj = parent === undefined ? new THREE.ExtrudeBufferGeometry(
+ json.parameters.shapes,
+ json.parameters.options
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ExtrudeBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/GeometriesSerializer.js b/ShadowEditor.Core/src/serialization/geometry/GeometriesSerializer.js
new file mode 100644
index 00000000..d23d1164
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/GeometriesSerializer.js
@@ -0,0 +1,86 @@
+import BaseSerializer from '../BaseSerializer';
+
+import BoxBufferGeometrySerializer from './BoxBufferGeometrySerializer';
+import CircleBufferGeometrySerializer from './CircleBufferGeometrySerializer';
+import ConeBufferGeometrySerializer from './ConeBufferGeometrySerializer';
+import CylinderBufferGeometrySerializer from './CylinderBufferGeometrySerializer';
+import DodecahedronBufferGeometrySerializer from './DodecahedronBufferGeometrySerializer';
+import ExtrudeBufferGeometrySerializer from './ExtrudeBufferGeometrySerializer';
+import IcosahedronBufferGeometrySerializer from './IcosahedronBufferGeometrySerializer';
+import InstancedBufferGeometrySerializer from './InstancedBufferGeometrySerializer';
+import LatheBufferGeometrySerializer from './LatheBufferGeometrySerializer';
+import OctahedronBufferGeometrySerializer from './OctahedronBufferGeometrySerializer';
+import ParametricBufferGeometrySerializer from './ParametricBufferGeometrySerializer';
+import PlaneBufferGeometrySerializer from './PlaneBufferGeometrySerializer';
+import PolyhedronBufferGeometrySerializer from './PolyhedronBufferGeometrySerializer';
+import RingBufferGeometrySerializer from './RingBufferGeometrySerializer';
+import ShapeBufferGeometrySerializer from './ShapeBufferGeometrySerializer';
+import SphereBufferGeometrySerializer from './SphereBufferGeometrySerializer';
+import TeapotBufferGeometrySerializer from './TeapotBufferGeometrySerializer';
+import TetrahedronBufferGeometrySerializer from './TetrahedronBufferGeometrySerializer';
+import TextBufferGeometrySerializer from './TextBufferGeometrySerializer';
+import TorusBufferGeometrySerializer from './TorusBufferGeometrySerializer';
+import TorusKnotBufferGeometrySerializer from './TorusKnotBufferGeometrySerializer';
+import TubeBufferGeometrySerializer from './TubeBufferGeometrySerializer';
+
+var Serializers = {
+ 'BoxBufferGeometry': BoxBufferGeometrySerializer,
+ 'CircleBufferGeometry': CircleBufferGeometrySerializer,
+ 'ConeBufferGeometry': ConeBufferGeometrySerializer,
+ 'CylinderBufferGeometry': CylinderBufferGeometrySerializer,
+ 'DodecahedronBufferGeometry': DodecahedronBufferGeometrySerializer,
+ 'ExtrudeBufferGeometry': ExtrudeBufferGeometrySerializer,
+ 'IcosahedronBufferGeometry': IcosahedronBufferGeometrySerializer,
+ 'InstancedBufferGeometry': InstancedBufferGeometrySerializer,
+ 'LatheBufferGeometry': LatheBufferGeometrySerializer,
+ 'OctahedronBufferGeometry': OctahedronBufferGeometrySerializer,
+ 'ParametricBufferGeometry': ParametricBufferGeometrySerializer,
+ 'PlaneBufferGeometry': PlaneBufferGeometrySerializer,
+ 'PolyhedronBufferGeometry': PolyhedronBufferGeometrySerializer,
+ 'RingBufferGeometry': RingBufferGeometrySerializer,
+ 'ShapeBufferGeometry': ShapeBufferGeometrySerializer,
+ 'SphereBufferGeometry': SphereBufferGeometrySerializer,
+ 'TeapotBufferGeometry': TeapotBufferGeometrySerializer,
+ 'TetrahedronBufferGeometry': TetrahedronBufferGeometrySerializer,
+ 'TextBufferGeometry': TextBufferGeometrySerializer,
+ 'TorusBufferGeometry': TorusBufferGeometrySerializer,
+ 'TorusKnotBufferGeometry': TorusKnotBufferGeometrySerializer,
+ 'TubeBufferGeometry': TubeBufferGeometrySerializer
+};
+
+/**
+ * GeometriesSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function GeometriesSerializer() {
+ BaseSerializer.call(this);
+}
+
+GeometriesSerializer.prototype = Object.create(BaseSerializer.prototype);
+GeometriesSerializer.prototype.constructor = GeometriesSerializer;
+
+GeometriesSerializer.prototype.toJSON = function (obj) {
+ var serializer = Serializers[obj.type];
+
+ if (serializer === undefined) {
+ console.warn(`GeometriesSerializer: 无法序列化 ${obj.type}。`);
+ return null;
+ }
+
+ return (new serializer()).toJSON(obj);
+};
+
+GeometriesSerializer.prototype.fromJSON = function (json, parent) {
+ var generator = json.metadata.generator;
+
+ var serializer = Serializers[generator.replace('Serializer', '')];
+
+ if (serializer === undefined) {
+ console.warn(`GeometriesSerializer: 不存在 ${generator} 的反序列化器`);
+ return null;
+ }
+
+ return (new serializer()).fromJSON(json, parent);
+};
+
+export default GeometriesSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/GeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/GeometrySerializer.js
new file mode 100644
index 00000000..c8a1f37d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/GeometrySerializer.js
@@ -0,0 +1,78 @@
+import BaseSerializer from '../BaseSerializer';
+
+/**
+ * GeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function GeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+GeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+GeometrySerializer.prototype.constructor = GeometrySerializer;
+
+GeometrySerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.type = obj.type;
+ json.boundingBox = obj.boundingBox;
+ json.boundingSphere = obj.boundingSphere;
+ json.colors = obj.colors;
+ json.colorsNeedUpdate = obj.colorsNeedUpdate;
+ json.faces = obj.faces;
+ json.faceVertexUvs = obj.faceVertexUvs;
+ json.groupsNeedUpdate = obj.groupsNeedUpdate;
+ json.isGeometry = obj.isGeometry;
+ json.lineDistances = obj.lineDistances;
+ json.lineDistancesNeedUpdate = obj.lineDistancesNeedUpdate;
+ json.morphTargets = obj.morphTargets;
+ json.morphNormals = obj.morphNormals;
+ json.name = obj.name;
+ json.normalsNeedUpdate = obj.normalsNeedUpdate;
+ json.parameters = obj.parameters;
+ json.skinWeights = obj.skinWeights;
+ json.skinIndices = obj.skinIndices;
+ json.uuid = obj.uuid;
+ json.vertices = obj.vertices;
+ json.verticesNeedUpdate = obj.verticesNeedUpdate;
+ json.elementsNeedUpdate = obj.elementsNeedUpdate;
+ json.uvsNeedUpdate = obj.uvsNeedUpdate;
+ json.normalsNeedUpdate = obj.normalsNeedUpdate;
+
+ return json;
+};
+
+GeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Geometry() : parent;
+
+ BaseSerializer.prototype.fromJSON.call(this, obj);
+
+ obj.type = json.type;
+ obj.boundingBox = json.boundingBox;
+ obj.boundingSphere = json.boundingSphere;
+ obj.colors = json.colors;
+ obj.colorsNeedUpdate = json.colorsNeedUpdate;
+ obj.faces = json.faces;
+ obj.faceVertexUvs = json.faceVertexUvs;
+ obj.groupsNeedUpdate = json.groupsNeedUpdate;
+ obj.isGeometry = json.isGeometry;
+ obj.lineDistances = json.lineDistances;
+ obj.lineDistancesNeedUpdate = json.lineDistancesNeedUpdate;
+ obj.morphTargets = json.morphTargets;
+ obj.morphNormals = json.morphNormals;
+ obj.name = json.name;
+ obj.normalsNeedUpdate = json.normalsNeedUpdate;
+ obj.parameters = json.parameters;
+ obj.skinWeights = json.skinWeights;
+ obj.skinIndices = json.skinIndices;
+ obj.uuid = json.uuid;
+ obj.vertices = json.vertices;
+ obj.verticesNeedUpdate = json.verticesNeedUpdate;
+ obj.elementsNeedUpdate = json.elementsNeedUpdate;
+ obj.uvsNeedUpdate = json.uvsNeedUpdate;
+ obj.normalsNeedUpdate = json.normalsNeedUpdate;
+
+ return obj;
+};
+
+export default GeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/IcosahedronBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/IcosahedronBufferGeometrySerializer.js
new file mode 100644
index 00000000..a2cfcabb
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/IcosahedronBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * IcosahedronBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function IcosahedronBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+IcosahedronBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+IcosahedronBufferGeometrySerializer.prototype.constructor = IcosahedronBufferGeometrySerializer;
+
+IcosahedronBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+IcosahedronBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.IcosahedronBufferGeometry(
+ json.parameters.radius,
+ json.parameters.detail
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default IcosahedronBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/InstancedBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/InstancedBufferGeometrySerializer.js
new file mode 100644
index 00000000..f6144f89
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/InstancedBufferGeometrySerializer.js
@@ -0,0 +1,29 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * InstancedBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function InstancedBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+InstancedBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+InstancedBufferGeometrySerializer.prototype.constructor = InstancedBufferGeometrySerializer;
+
+InstancedBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+InstancedBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.InstancedBufferGeometry() : parent;
+
+ // TODO:
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default InstancedBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/LatheBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/LatheBufferGeometrySerializer.js
new file mode 100644
index 00000000..3499e1fa
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/LatheBufferGeometrySerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * LatheBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LatheBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+LatheBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+LatheBufferGeometrySerializer.prototype.constructor = LatheBufferGeometrySerializer;
+
+LatheBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+LatheBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.LatheBufferGeometry(
+ json.parameters.points,
+ json.parameters.segments,
+ json.parameters.phiStart,
+ json.parameters.phiLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default LatheBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/OctahedronBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/OctahedronBufferGeometrySerializer.js
new file mode 100644
index 00000000..08276a18
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/OctahedronBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * OctahedronBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function OctahedronBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+OctahedronBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+OctahedronBufferGeometrySerializer.prototype.constructor = OctahedronBufferGeometrySerializer;
+
+OctahedronBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+OctahedronBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.OctahedronBufferGeometry(
+ json.parameters.radius,
+ json.parameters.detail
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default OctahedronBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/ParametricBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/ParametricBufferGeometrySerializer.js
new file mode 100644
index 00000000..63e66515
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/ParametricBufferGeometrySerializer.js
@@ -0,0 +1,31 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * ParametricBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ParametricBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+ParametricBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+ParametricBufferGeometrySerializer.prototype.constructor = ParametricBufferGeometrySerializer;
+
+ParametricBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+ParametricBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ParametricBufferGeometry(
+ json.parameters.func,
+ json.parameters.slices,
+ json.parameters.stacks
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ParametricBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/PlaneBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/PlaneBufferGeometrySerializer.js
new file mode 100644
index 00000000..62c350be
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/PlaneBufferGeometrySerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * PlaneBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PlaneBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+PlaneBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+PlaneBufferGeometrySerializer.prototype.constructor = PlaneBufferGeometrySerializer;
+
+PlaneBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+PlaneBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PlaneBufferGeometry(
+ json.parameters.width,
+ json.parameters.height,
+ json.parameters.widthSegments,
+ json.parameters.heightSegments
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default PlaneBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/PolyhedronBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/PolyhedronBufferGeometrySerializer.js
new file mode 100644
index 00000000..4bdbc119
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/PolyhedronBufferGeometrySerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * PolyhedronBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PolyhedronBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+PolyhedronBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+PolyhedronBufferGeometrySerializer.prototype.constructor = PolyhedronBufferGeometrySerializer;
+
+PolyhedronBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+PolyhedronBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PolyhedronBufferGeometry(
+ json.parameters.vertices,
+ json.parameters.indices,
+ json.parameters.radius,
+ json.parameters.detail
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default PolyhedronBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/RingBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/RingBufferGeometrySerializer.js
new file mode 100644
index 00000000..1f879d57
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/RingBufferGeometrySerializer.js
@@ -0,0 +1,34 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * RingBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function RingBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+RingBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+RingBufferGeometrySerializer.prototype.constructor = RingBufferGeometrySerializer;
+
+RingBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+RingBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.RingBufferGeometry(
+ json.parameters.innerRadius,
+ json.parameters.outerRadius,
+ json.parameters.thetaSegments,
+ json.parameters.phiSegments,
+ json.parameters.thetaStart,
+ json.parameters.thetaLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default RingBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/ShapeBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/ShapeBufferGeometrySerializer.js
new file mode 100644
index 00000000..b5d83bc4
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/ShapeBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * ShapeBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ShapeBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+ShapeBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+ShapeBufferGeometrySerializer.prototype.constructor = ShapeBufferGeometrySerializer;
+
+ShapeBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+ShapeBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ShapeBufferGeometry(
+ json.parameters.shapes,
+ json.parameters.curveSegments
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ShapeBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/SphereBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/SphereBufferGeometrySerializer.js
new file mode 100644
index 00000000..bff42c4b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/SphereBufferGeometrySerializer.js
@@ -0,0 +1,35 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * SphereBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SphereBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+SphereBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+SphereBufferGeometrySerializer.prototype.constructor = SphereBufferGeometrySerializer;
+
+SphereBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+SphereBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.SphereBufferGeometry(
+ json.parameters.radius,
+ json.parameters.widthSegments,
+ json.parameters.heightSegments,
+ json.parameters.phiStart,
+ json.parameters.phiLength,
+ json.parameters.thetaStart,
+ json.parameters.thetaLength
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default SphereBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TeapotBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TeapotBufferGeometrySerializer.js
new file mode 100644
index 00000000..6ad7ca5b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TeapotBufferGeometrySerializer.js
@@ -0,0 +1,35 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TeapotBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TeapotBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TeapotBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TeapotBufferGeometrySerializer.prototype.constructor = TeapotBufferGeometrySerializer;
+
+TeapotBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TeapotBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TeapotBufferGeometry(
+ json.parameters.size,
+ json.parameters.segments,
+ json.parameters.bottom,
+ json.parameters.lid,
+ json.parameters.body,
+ json.parameters.fitLid,
+ json.parameters.blinn
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TeapotBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TetrahedronBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TetrahedronBufferGeometrySerializer.js
new file mode 100644
index 00000000..5f88cb27
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TetrahedronBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TetrahedronBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TetrahedronBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TetrahedronBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TetrahedronBufferGeometrySerializer.prototype.constructor = TetrahedronBufferGeometrySerializer;
+
+TetrahedronBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TetrahedronBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TetrahedronBufferGeometry(
+ json.parameters.radius,
+ json.parameters.detail
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TetrahedronBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TextBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TextBufferGeometrySerializer.js
new file mode 100644
index 00000000..237a0ab6
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TextBufferGeometrySerializer.js
@@ -0,0 +1,30 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TextBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TextBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TextBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TextBufferGeometrySerializer.prototype.constructor = TextBufferGeometrySerializer;
+
+TextBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TextBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TextBufferGeometry(
+ json.parameters.text,
+ json.parameters.parameters
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TextBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TorusBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TorusBufferGeometrySerializer.js
new file mode 100644
index 00000000..a8e21413
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TorusBufferGeometrySerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TorusBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TorusBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TorusBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TorusBufferGeometrySerializer.prototype.constructor = TorusBufferGeometrySerializer;
+
+TorusBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TorusBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TorusBufferGeometry(
+ json.parameters.radius,
+ json.parameters.tube,
+ json.parameters.radialSegments,
+ json.parameters.tubularSegments,
+ json.parameters.arc
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TorusBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TorusKnotBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TorusKnotBufferGeometrySerializer.js
new file mode 100644
index 00000000..319429f1
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TorusKnotBufferGeometrySerializer.js
@@ -0,0 +1,34 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TorusKnotBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TorusKnotBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TorusKnotBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TorusKnotBufferGeometrySerializer.prototype.constructor = TorusKnotBufferGeometrySerializer;
+
+TorusKnotBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TorusKnotBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TorusKnotBufferGeometry(
+ json.parameters.radius,
+ json.parameters.tube,
+ json.parameters.tubularSegments,
+ json.parameters.radialSegments,
+ json.parameters.p,
+ json.parameters.q
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TorusKnotBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/geometry/TubeBufferGeometrySerializer.js b/ShadowEditor.Core/src/serialization/geometry/TubeBufferGeometrySerializer.js
new file mode 100644
index 00000000..2771aed7
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/geometry/TubeBufferGeometrySerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+import BufferGeometrySerializer from './BufferGeometrySerializer';
+
+/**
+ * TubeBufferGeometrySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TubeBufferGeometrySerializer() {
+ BaseSerializer.call(this);
+}
+
+TubeBufferGeometrySerializer.prototype = Object.create(BaseSerializer.prototype);
+TubeBufferGeometrySerializer.prototype.constructor = TubeBufferGeometrySerializer;
+
+TubeBufferGeometrySerializer.prototype.toJSON = function (obj) {
+ return BufferGeometrySerializer.prototype.toJSON.call(this, obj);
+};
+
+TubeBufferGeometrySerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.TubeBufferGeometry(
+ json.parameters.path,
+ json.parameters.tubularSegments,
+ json.parameters.radius,
+ json.parameters.radialSegments,
+ json.parameters.closed
+ ) : parent;
+
+ BufferGeometrySerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default TubeBufferGeometrySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/AmbientLightSerializer.js b/ShadowEditor.Core/src/serialization/light/AmbientLightSerializer.js
new file mode 100644
index 00000000..58fa6afd
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/AmbientLightSerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * AmbientLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function AmbientLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+AmbientLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+AmbientLightSerializer.prototype.constructor = AmbientLightSerializer;
+
+AmbientLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.isAmbientLight = obj.isAmbientLight;
+
+ return json;
+};
+
+AmbientLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.AmbientLight(json.color, json.intensity) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isAmbientLight = json.isAmbientLight;
+
+ return obj;
+};
+
+export default AmbientLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/DirectionalLightSerializer.js b/ShadowEditor.Core/src/serialization/light/DirectionalLightSerializer.js
new file mode 100644
index 00000000..b79906aa
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/DirectionalLightSerializer.js
@@ -0,0 +1,33 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * DirectionalLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function DirectionalLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+DirectionalLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+DirectionalLightSerializer.prototype.constructor = DirectionalLightSerializer;
+
+DirectionalLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.isDirectionalLight = obj.isDirectionalLight;
+
+ return json;
+};
+
+DirectionalLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.DirectionalLight(json.color, json.intensity) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isDirectionalLight = json.isDirectionalLight;
+
+ return obj;
+};
+
+export default DirectionalLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/HemisphereLightSerializer.js b/ShadowEditor.Core/src/serialization/light/HemisphereLightSerializer.js
new file mode 100644
index 00000000..533e6e1e
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/HemisphereLightSerializer.js
@@ -0,0 +1,35 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * HemisphereLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function HemisphereLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+HemisphereLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+HemisphereLightSerializer.prototype.constructor = HemisphereLightSerializer;
+
+HemisphereLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.isHemisphereLight = obj.isHemisphereLight;
+ json.skyColor = obj.skyColor;
+ json.groundColor = obj.groundColor;
+
+ return json;
+};
+
+HemisphereLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.HemisphereLight(json.skyColor, json.groundColor, json.intensity) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isHemisphereLight = json.isHemisphereLight;
+
+ return obj;
+};
+
+export default HemisphereLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/LightSerializer.js b/ShadowEditor.Core/src/serialization/light/LightSerializer.js
new file mode 100644
index 00000000..272964d0
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/LightSerializer.js
@@ -0,0 +1,43 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+import LightShadowsSerializer from './shadow/LightShadowsSerializer';
+
+/**
+ * LightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LightSerializer() {
+ BaseSerializer.call(this);
+}
+
+LightSerializer.prototype = Object.create(BaseSerializer.prototype);
+LightSerializer.prototype.constructor = LightSerializer;
+
+LightSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.color = obj.color;
+ json.intensity = obj.intensity;
+ json.isLight = obj.isLight;
+ json.shadow = obj.shadow == null ? null : (new LightShadowsSerializer()).toJSON(obj.shadow);
+
+ return json;
+};
+
+LightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Light() : parent;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.color = new THREE.Color(json.color);
+ obj.intensity = json.intensity;
+ obj.isLight = json.isLight;
+
+ if (json.shadow) {
+ obj.shadow = (new LightShadowsSerializer()).fromJSON(json.shadow);
+ }
+
+ return obj;
+};
+
+export default LightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/PointLightSerializer.js b/ShadowEditor.Core/src/serialization/light/PointLightSerializer.js
new file mode 100644
index 00000000..d3b2ae80
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/PointLightSerializer.js
@@ -0,0 +1,35 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * PointLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PointLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+PointLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+PointLightSerializer.prototype.constructor = PointLightSerializer;
+
+PointLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.isPointLight = obj.isPointLight;
+ json.distance = obj.distance;
+ json.decay = obj.decay;
+
+ return json;
+};
+
+PointLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PointLight(json.color, json.intensity, json.distance, json.decay) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isPointLight = json.isPointLight;
+
+ return obj;
+};
+
+export default PointLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/RectAreaLightSerializer.js b/ShadowEditor.Core/src/serialization/light/RectAreaLightSerializer.js
new file mode 100644
index 00000000..4d54126c
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/RectAreaLightSerializer.js
@@ -0,0 +1,34 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * RectAreaLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function RectAreaLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+RectAreaLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+RectAreaLightSerializer.prototype.constructor = RectAreaLightSerializer;
+
+RectAreaLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.width = obj.width;
+ json.height = obj.height;
+
+ return json;
+};
+
+RectAreaLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.RectAreaLight(json.color, json.intensity, json.width, json.height) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isRectAreaLight = true;
+
+ return obj;
+};
+
+export default RectAreaLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/SpotLightSerializer.js b/ShadowEditor.Core/src/serialization/light/SpotLightSerializer.js
new file mode 100644
index 00000000..a05999c1
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/SpotLightSerializer.js
@@ -0,0 +1,47 @@
+import BaseSerializer from '../BaseSerializer';
+import LightSerializer from './LightSerializer';
+
+/**
+ * SpotLightSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SpotLightSerializer() {
+ BaseSerializer.call(this);
+}
+
+SpotLightSerializer.prototype = Object.create(BaseSerializer.prototype);
+SpotLightSerializer.prototype.constructor = SpotLightSerializer;
+
+SpotLightSerializer.prototype.toJSON = function (obj) {
+ var json = LightSerializer.prototype.toJSON.call(this, obj);
+
+ json.isSpotLight = obj.isSpotLight;
+ json.distance = obj.distance;
+ json.angle = obj.angle;
+ json.penumbra = obj.penumbra;
+ json.decay = obj.decay;
+
+ return json;
+};
+
+SpotLightSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.SpotLight(
+ json.color,
+ json.intensity,
+ json.distance,
+ json.angle,
+ json.penumbra,
+ json.decay) : parent;
+
+ LightSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.isSpotLight = json.isSpotLight;
+ obj.distance = json.distance;
+ obj.angle = json.angle;
+ obj.penumbra = json.penumbra;
+ obj.decay = json.decay;
+
+ return obj;
+};
+
+export default SpotLightSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/shadow/DirectionalLightShadowSerializer.js b/ShadowEditor.Core/src/serialization/light/shadow/DirectionalLightShadowSerializer.js
new file mode 100644
index 00000000..a6c572d2
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/shadow/DirectionalLightShadowSerializer.js
@@ -0,0 +1,31 @@
+import BaseSerializer from '../../BaseSerializer';
+import LightShadowSerializer from './LightShadowSerializer';
+
+/**
+ * DirectionalLightShadowSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function DirectionalLightShadowSerializer() {
+ BaseSerializer.call(this);
+}
+
+DirectionalLightShadowSerializer.prototype = Object.create(BaseSerializer.prototype);
+DirectionalLightShadowSerializer.prototype.constructor = DirectionalLightShadowSerializer;
+
+DirectionalLightShadowSerializer.prototype.toJSON = function (obj) {
+ var json = LightShadowSerializer.prototype.toJSON.call(this, obj);
+
+ json.isDirectionalLightShadow = obj.isDirectionalLightShadow;
+
+ return json;
+};
+
+DirectionalLightShadowSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.DirectionalLightShadow() : parent;
+
+ LightShadowSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default DirectionalLightShadowSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/shadow/LightShadowSerializer.js b/ShadowEditor.Core/src/serialization/light/shadow/LightShadowSerializer.js
new file mode 100644
index 00000000..8d8d35ea
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/shadow/LightShadowSerializer.js
@@ -0,0 +1,45 @@
+import BaseSerializer from '../../BaseSerializer';
+import CamerasSerializer from '../../camera/CamerasSerializer';
+import WebGLRenderTargetSerializer from '../../core/WebGLRenderTargetSerializer';
+
+/**
+ * LightShadowSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LightShadowSerializer() {
+ BaseSerializer.call(this);
+}
+
+LightShadowSerializer.prototype = Object.create(BaseSerializer.prototype);
+LightShadowSerializer.prototype.constructor = LightShadowSerializer;
+
+LightShadowSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.bias = obj.bias;
+ json.camera = (new CamerasSerializer()).toJSON(obj.camera);
+ json.map = obj.map == null ? null : (new WebGLRenderTargetSerializer()).toJSON(obj.map);
+ json.mapSize = obj.mapSize;
+ json.matrix = obj.matrix;
+ json.radius = obj.radius;
+
+ return json;
+};
+
+LightShadowSerializer.prototype.fromJSON = function (json, parent) {
+ var camera = (new CamerasSerializer()).fromJSON(json.camera);
+
+ var obj = parent === undefined ? new THREE.LightShadow(camera) : parent;
+
+ obj.bias = json.bias;
+ obj.camera.copy(camera);
+ // 纹理时自动生成的,不要反序列化
+ // obj.map = json.map == null ? null : (new WebGLRenderTargetSerializer()).fromJSON(json.map);
+ obj.mapSize.copy(json.mapSize);
+ obj.matrix.copy(json.matrix);
+ obj.radius = json.radius;
+
+ return obj;
+};
+
+export default LightShadowSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/shadow/LightShadowsSerializer.js b/ShadowEditor.Core/src/serialization/light/shadow/LightShadowsSerializer.js
new file mode 100644
index 00000000..eff45678
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/shadow/LightShadowsSerializer.js
@@ -0,0 +1,48 @@
+import BaseSerializer from '../../BaseSerializer';
+
+import LightShadowSerializer from './LightShadowSerializer';
+import DirectionalLightShadowSerializer from './DirectionalLightShadowSerializer';
+import SpotLightShadowSerializer from './SpotLightShadowSerializer';
+
+var Serializers = {
+ 'LightShadow': LightShadowSerializer,
+ 'DirectionalLightShadow': DirectionalLightShadowSerializer,
+ 'SpotLightShadow': SpotLightShadowSerializer
+};
+
+/**
+ * LightShadowsSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LightShadowsSerializer() {
+ BaseSerializer.call(this);
+}
+
+LightShadowsSerializer.prototype = Object.create(BaseSerializer.prototype);
+LightShadowsSerializer.prototype.constructor = LightShadowsSerializer;
+
+LightShadowsSerializer.prototype.toJSON = function (obj) {
+ var serializer = Serializers[obj.constructor.name];
+
+ if (serializer === undefined) {
+ console.warn(`LightShadowsSerializer: 无法序列化${obj.constructor.name}。`);
+ return null;
+ }
+
+ return (new serializer()).toJSON(obj);
+};
+
+LightShadowsSerializer.prototype.fromJSON = function (json) {
+ var generator = json.metadata.generator;
+
+ var serializer = Serializers[generator.replace('Serializer', '')];
+
+ if (serializer === undefined) {
+ console.warn(`LightShadowsSerializer: 不存在 ${generator} 的反序列化器`);
+ return null;
+ }
+
+ return (new serializer()).fromJSON(json);
+};
+
+export default LightShadowsSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/light/shadow/SpotLightShadowSerializer.js b/ShadowEditor.Core/src/serialization/light/shadow/SpotLightShadowSerializer.js
new file mode 100644
index 00000000..a2f8c177
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/light/shadow/SpotLightShadowSerializer.js
@@ -0,0 +1,31 @@
+import BaseSerializer from '../../BaseSerializer';
+import LightShadowSerializer from './LightShadowSerializer';
+
+/**
+ * SpotLightShadowSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SpotLightShadowSerializer() {
+ BaseSerializer.call(this);
+}
+
+SpotLightShadowSerializer.prototype = Object.create(BaseSerializer.prototype);
+SpotLightShadowSerializer.prototype.constructor = SpotLightShadowSerializer;
+
+SpotLightShadowSerializer.prototype.toJSON = function (obj) {
+ var json = LightShadowSerializer.prototype.toJSON.call(this, obj);
+
+ json.isSpotLightShadow = obj.isSpotLightShadow;
+
+ return json;
+};
+
+SpotLightShadowSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.SpotLightShadow() : parent;
+
+ LightShadowSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default SpotLightShadowSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/LineBasicMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/LineBasicMaterialSerializer.js
new file mode 100644
index 00000000..83a7bd43
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/LineBasicMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * LineBasicMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LineBasicMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+LineBasicMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+LineBasicMaterialSerializer.prototype.constructor = LineBasicMaterialSerializer;
+
+LineBasicMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+LineBasicMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.LineBasicMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default LineBasicMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/LineDashedMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/LineDashedMaterialSerializer.js
new file mode 100644
index 00000000..1133c73d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/LineDashedMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * LineDashedMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function LineDashedMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+LineDashedMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+LineDashedMaterialSerializer.prototype.constructor = LineDashedMaterialSerializer;
+
+LineDashedMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+LineDashedMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.LineDashedMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default LineDashedMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MaterialSerializer.js
new file mode 100644
index 00000000..b677cce1
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MaterialSerializer.js
@@ -0,0 +1,164 @@
+import BaseSerializer from '../BaseSerializer';
+
+import TexturesSerializer from '../texture/TexturesSerializer';
+
+/**
+ * MaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MaterialSerializer.prototype.constructor = MaterialSerializer;
+
+MaterialSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.alphaMap = obj.alphaMap == null ? null : (new TexturesSerializer()).toJSON(obj.alphaMap);
+ json.alphaTest = obj.alphaTest;
+ json.aoMap = obj.aoMap == null ? null : (new TexturesSerializer()).toJSON(obj.aoMap);
+ json.aoMapIntensity = obj.aoMapIntensity;
+ json.blendDst = obj.blendDst;
+ json.blendDstAlpha = obj.blendDstAlpha;
+ json.blendEquation = obj.blendEquation;
+ json.blendEquationAlpha = obj.blendEquationAlpha;
+ json.blendSrc = obj.blendSrc;
+ json.blendSrcAlpha = obj.blendSrcAlpha;
+ json.blending = obj.blending;
+ json.bumpMap = obj.bumpMap == null ? null : (new TexturesSerializer()).toJSON(obj.bumpMap);
+ json.bumpScale = obj.bumpScale;
+ json.clipIntersection = obj.clipIntersection;
+ json.clipShadow = obj.clipShadow;
+ json.clippingPlanes = obj.clippingPlanes;
+ json.color = obj.color;
+ json.colorWrite = obj.colorWrite;
+ json.depthFunc = obj.depthFunc;
+ json.depthTest = obj.depthTest;
+ json.depthWrite = obj.depthWrite;
+ json.displacementBias = obj.displacementBias;
+ json.displacementMap = obj.displacementMap == null ? null : (new TexturesSerializer()).toJSON(obj.displacementMap);
+ json.displacementScale = obj.displacementScale;
+ json.dithering = obj.dithering;
+ json.emissive = obj.emissive;
+ json.emissiveIntensity = obj.emissiveIntensity;
+ json.emissiveMap = obj.emissiveMap == null ? null : (new TexturesSerializer()).toJSON(obj.emissiveMap);
+ json.envMap = obj.envMap == null ? null : (new TexturesSerializer()).toJSON(obj.envMap);
+ json.envMapIntensity = obj.envMapIntensity;
+ json.flatShading = obj.flatShading;
+ json.fog = obj.fog;
+ json.lightMap = obj.lightMap == null ? null : (new TexturesSerializer()).toJSON(obj.lightMap);
+ json.lightMapIntensity = obj.lightMapIntensity;
+ json.lights = obj.lights;
+ json.linewidth = obj.linewidth;
+ json.map = obj.map == null ? null : (new TexturesSerializer()).toJSON(obj.map);
+ json.metalness = obj.metalness;
+ json.metalnessMap = obj.metalnessMap == null ? null : (new TexturesSerializer()).toJSON(obj.metalnessMap);
+ json.morphNormals = obj.morphNormals;
+ json.morphTargets = obj.morphTargets;
+ json.name = obj.name;
+ json.normalMap = obj.normalMap == null ? null : (new TexturesSerializer()).toJSON(obj.normalMap);
+ json.normalScale = obj.normalScale;
+ json.opacity = obj.opacity;
+ json.overdraw = obj.overdraw;
+ json.polygonOffset = obj.polygonOffset;
+ json.polygonOffsetFactor = obj.polygonOffsetFactor;
+ json.polygonOffsetUnits = obj.polygonOffsetUnits;
+ json.precision = obj.precision;
+ json.premultipliedAlpha = obj.premultipliedAlpha;
+ json.refractionRatio = obj.refractionRatio;
+ json.roughness = obj.roughness;
+ json.roughnessMap = obj.roughnessMap == null ? null : (new TexturesSerializer()).toJSON(obj.roughnessMap);
+ json.shadowSide = obj.shadowSide;
+ json.side = obj.side;
+ json.skinning = obj.skinning;
+ json.transparent = obj.transparent;
+ json.type = obj.type;
+ json.userData = obj.userData;
+ json.uuid = obj.uuid;
+ json.vertexColors = obj.vertexColors;
+ json.visible = obj.visible;
+ json.wireframe = obj.wireframe;
+ json.wireframeLinecap = obj.wireframeLinecap;
+ json.wireframeLinejoin = obj.wireframeLinejoin;
+ json.wireframeLinewidth = obj.wireframeLinewidth;
+
+ return json;
+};
+
+MaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.Material() : parent;
+
+ obj.alphaMap = json.alphaMap == null ? null : (new TexturesSerializer()).fromJSON(json.alphaMap);
+ obj.alphaTest = json.alphaTest;
+ obj.aoMap = json.aoMap == null ? null : (new TexturesSerializer()).fromJSON(json.aoMap);
+ obj.aoMapIntensity = json.aoMapIntensity;
+ obj.blendDst = json.blendDst;
+ obj.blendDstAlpha = json.blendDstAlpha;
+ obj.blendEquation = json.blendEquation;
+ obj.blendEquationAlpha = json.blendEquationAlpha;
+ obj.blendSrc = json.blendSrc;
+ obj.blendSrcAlpha = json.blendSrcAlpha;
+ obj.blending = json.blending;
+ obj.bumpMap = json.bumpMap == null ? null : (new TexturesSerializer()).fromJSON(json.bumpMap);
+ obj.bumpScale = json.bumpScale;
+ obj.clipIntersection = json.clipIntersection;
+ obj.clipShadow = json.clipShadow;
+ obj.clippingPlanes = json.clippingPlanes;
+ obj.color = json.color == null ? null : new THREE.Color(json.color);
+ obj.colorWrite = json.colorWrite;
+ obj.depthFunc = json.depthFunc;
+ obj.depthTest = json.depthTest;
+ obj.depthWrite = json.depthWrite;
+ obj.displacementBias = json.displacementBias;
+ obj.displacementMap = json.displacementMap == null ? null : (new TexturesSerializer()).fromJSON(json.displacementMap);
+ obj.displacementScale = json.displacementScale;
+ obj.dithering = json.dithering;
+ obj.emissive = json.emissive == null ? undefined : new THREE.Color(json.emissive);
+ obj.emissiveIntensity = json.emissiveIntensity;
+ obj.emissiveMap = json.emissiveMap == null ? null : (new TexturesSerializer()).fromJSON(json.emissiveMap);
+ obj.envMap = json.envMap == null ? null : (new TexturesSerializer()).fromJSON(json.envMap);
+ obj.envMapIntensity = json.envMapIntensity;
+ obj.flatShading = json.flatShading;
+ obj.fog = json.fog;
+ obj.lightMap = json.lightMap == null ? null : (new TexturesSerializer()).fromJSON(json.lightMap);
+ obj.lightMapIntensity = json.lightMapIntensity;
+ obj.lights = json.lights;
+ obj.linewidth = json.linewidth;
+ obj.map = json.map == null ? null : (new TexturesSerializer()).fromJSON(json.map);
+ obj.metalness = json.metalness;
+ obj.metalnessMap = json.metalnessMap == null ? null : (new TexturesSerializer()).fromJSON(json.metalnessMap);
+ obj.morphNormals = json.morphNormals;
+ obj.morphTargets = json.morphTargets;
+ obj.name = json.name;
+ obj.normalMap = json.normalMap == null ? null : (new TexturesSerializer()).fromJSON(json.normalMap);
+ obj.normalScale = json.normalScale == null ? null : new THREE.Vector2().copy(json.normalScale);
+ obj.opacity = json.opacity;
+ obj.overdraw = json.overdraw;
+ obj.polygonOffset = json.polygonOffset;
+ obj.polygonOffsetFactor = json.polygonOffsetFactor;
+ obj.polygonOffsetUnits = json.polygonOffsetUnits;
+ obj.precision = json.precision;
+ obj.premultipliedAlpha = json.premultipliedAlpha;
+ obj.refractionRatio = json.refractionRatio;
+ obj.roughness = json.roughness;
+ obj.roughnessMap = json.roughnessMap == null ? null : (new TexturesSerializer()).fromJSON(json.roughnessMap);
+ obj.shadowSide = json.shadowSide;
+ obj.side = json.side;
+ obj.skinning = json.skinning;
+ obj.transparent = json.transparent;
+ obj.type = json.type;
+ obj.userData = json.userData;
+ obj.uuid = json.uuid;
+ obj.vertexColors = json.vertexColors;
+ obj.visible = json.visible;
+ obj.wireframe = json.wireframe;
+ obj.wireframeLinecap = json.wireframeLinecap;
+ obj.wireframeLinejoin = json.wireframeLinejoin;
+ obj.wireframeLinewidth = json.wireframeLinewidth;
+
+ return obj;
+};
+
+export default MaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MaterialsSerializer.js b/ShadowEditor.Core/src/serialization/material/MaterialsSerializer.js
new file mode 100644
index 00000000..c6e3c405
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MaterialsSerializer.js
@@ -0,0 +1,86 @@
+import BaseSerializer from '../BaseSerializer';
+
+import LineBasicMaterialSerializer from './LineBasicMaterialSerializer';
+import LineDashedMaterialSerializer from './LineDashedMaterialSerializer';
+import MeshBasicMaterialSerializer from './MeshBasicMaterialSerializer';
+import MeshDepthMaterialSerializer from './MeshDepthMaterialSerializer';
+import MeshDistanceMaterialSerializer from './MeshDistanceMaterialSerializer';
+import MeshFaceMaterialSerializer from './MeshFaceMaterialSerializer';
+import MeshLambertMaterialSerializer from './MeshLambertMaterialSerializer';
+import MeshNormalMaterialSerializer from './MeshNormalMaterialSerializer';
+import MeshPhongMaterialSerializer from './MeshPhongMaterialSerializer';
+import MeshPhysicalMaterialSerializer from './MeshPhysicalMaterialSerializer';
+import MeshStandardMaterialSerializer from './MeshStandardMaterialSerializer';
+import MeshToonMaterialSerializer from './MeshToonMaterialSerializer';
+import MultiMaterialSerializer from './MultiMaterialSerializer';
+import ParticleBasicMaterialSerializer from './ParticleBasicMaterialSerializer';
+import ParticleSystemMaterialSerializer from './ParticleSystemMaterialSerializer';
+import PointCloudMaterialSerializer from './PointCloudMaterialSerializer';
+import PointsMaterialSerializer from './PointsMaterialSerializer';
+import RawShaderMaterialSerializer from './RawShaderMaterialSerializer';
+import ShaderMaterialSerializer from './ShaderMaterialSerializer';
+import ShadowMaterialSerializer from './ShadowMaterialSerializer';
+import SpriteCanvasMaterialSerializer from './SpriteCanvasMaterialSerializer';
+import SpriteMaterialSerializer from './SpriteMaterialSerializer';
+
+var Serializers = {
+ 'LineBasicMaterial': LineBasicMaterialSerializer,
+ 'LineDashedMaterial': LineDashedMaterialSerializer,
+ 'MeshBasicMaterial': MeshBasicMaterialSerializer,
+ 'MeshDepthMaterial': MeshDepthMaterialSerializer,
+ 'MeshDistanceMaterial': MeshDistanceMaterialSerializer,
+ 'MeshFaceMaterial': MeshFaceMaterialSerializer,
+ 'MeshLambertMaterial': MeshLambertMaterialSerializer,
+ 'MeshNormalMaterial': MeshNormalMaterialSerializer,
+ 'MeshPhongMaterial': MeshPhongMaterialSerializer,
+ 'MeshPhysicalMaterial': MeshPhysicalMaterialSerializer,
+ 'MeshStandardMaterial': MeshStandardMaterialSerializer,
+ 'MeshToonMaterial': MeshToonMaterialSerializer,
+ 'MultiMaterial': MultiMaterialSerializer,
+ 'ParticleBasicMaterial': ParticleBasicMaterialSerializer,
+ 'ParticleSystemMaterial': ParticleSystemMaterialSerializer,
+ 'PointCloudMaterial': PointCloudMaterialSerializer,
+ 'PointsMaterial': PointsMaterialSerializer,
+ 'RawShaderMaterial': RawShaderMaterialSerializer,
+ 'ShaderMaterial': ShaderMaterialSerializer,
+ 'ShadowMaterial': ShadowMaterialSerializer,
+ 'SpriteCanvasMaterial': SpriteCanvasMaterialSerializer,
+ 'SpriteMaterial': SpriteMaterialSerializer
+};
+
+/**
+ * MaterialsSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MaterialsSerializer() {
+ BaseSerializer.call(this);
+}
+
+MaterialsSerializer.prototype = Object.create(BaseSerializer.prototype);
+MaterialsSerializer.prototype.constructor = MaterialsSerializer;
+
+MaterialsSerializer.prototype.toJSON = function (obj) {
+ var serializer = Serializers[obj.type];
+
+ if (serializer === undefined) {
+ console.warn(`MaterialsSerializer: 无法序列化${obj.type}。`);
+ return null;
+ }
+
+ return (new serializer()).toJSON(obj);
+};
+
+MaterialsSerializer.prototype.fromJSON = function (json, parent) {
+ var generator = json.metadata.generator;
+
+ var serializer = Serializers[generator.replace('Serializer', '')];
+
+ if (serializer === undefined) {
+ console.warn(`MaterialsSerializer: 不存在 ${generator} 的反序列化器。`);
+ return null;
+ }
+
+ return (new serializer()).fromJSON(json, parent);
+};
+
+export default MaterialsSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshBasicMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshBasicMaterialSerializer.js
new file mode 100644
index 00000000..6465f984
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshBasicMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshBasicMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshBasicMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshBasicMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshBasicMaterialSerializer.prototype.constructor = MeshBasicMaterialSerializer;
+
+MeshBasicMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshBasicMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshBasicMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshBasicMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshDepthMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshDepthMaterialSerializer.js
new file mode 100644
index 00000000..a6adacb2
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshDepthMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshDepthMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshDepthMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshDepthMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshDepthMaterialSerializer.prototype.constructor = MeshDepthMaterialSerializer;
+
+MeshDepthMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshDepthMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshDepthMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshDepthMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshDistanceMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshDistanceMaterialSerializer.js
new file mode 100644
index 00000000..b4b31211
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshDistanceMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshDistanceMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshDistanceMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshDistanceMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshDistanceMaterialSerializer.prototype.constructor = MeshDistanceMaterialSerializer;
+
+MeshDistanceMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshDistanceMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshDistanceMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshDistanceMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshFaceMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshFaceMaterialSerializer.js
new file mode 100644
index 00000000..6eac5f9b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshFaceMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshFaceMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshFaceMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshFaceMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshFaceMaterialSerializer.prototype.constructor = MeshFaceMaterialSerializer;
+
+MeshFaceMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshFaceMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshFaceMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshFaceMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshLambertMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshLambertMaterialSerializer.js
new file mode 100644
index 00000000..7f542b9e
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshLambertMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshLambertMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshLambertMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshLambertMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshLambertMaterialSerializer.prototype.constructor = MeshLambertMaterialSerializer;
+
+MeshLambertMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshLambertMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshLambertMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshLambertMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshNormalMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshNormalMaterialSerializer.js
new file mode 100644
index 00000000..9f08a10b
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshNormalMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshNormalMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshNormalMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshNormalMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshNormalMaterialSerializer.prototype.constructor = MeshNormalMaterialSerializer;
+
+MeshNormalMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshNormalMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshNormalMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshNormalMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshPhongMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshPhongMaterialSerializer.js
new file mode 100644
index 00000000..6788f51c
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshPhongMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshPhongMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshPhongMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshPhongMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshPhongMaterialSerializer.prototype.constructor = MeshPhongMaterialSerializer;
+
+MeshPhongMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshPhongMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshPhongMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshPhongMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshPhysicalMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshPhysicalMaterialSerializer.js
new file mode 100644
index 00000000..e627f6b2
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshPhysicalMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshPhysicalMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshPhysicalMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshPhysicalMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshPhysicalMaterialSerializer.prototype.constructor = MeshPhysicalMaterialSerializer;
+
+MeshPhysicalMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshPhysicalMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshPhysicalMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshPhysicalMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshStandardMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshStandardMaterialSerializer.js
new file mode 100644
index 00000000..e989c4c7
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshStandardMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshStandardMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshStandardMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshStandardMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshStandardMaterialSerializer.prototype.constructor = MeshStandardMaterialSerializer;
+
+MeshStandardMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshStandardMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshStandardMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshStandardMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MeshToonMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MeshToonMaterialSerializer.js
new file mode 100644
index 00000000..57dd9c45
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MeshToonMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MeshToonMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MeshToonMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MeshToonMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MeshToonMaterialSerializer.prototype.constructor = MeshToonMaterialSerializer;
+
+MeshToonMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MeshToonMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MeshToonMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MeshToonMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/MultiMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/MultiMaterialSerializer.js
new file mode 100644
index 00000000..c39fb855
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/MultiMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * MultiMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function MultiMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+MultiMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+MultiMaterialSerializer.prototype.constructor = MultiMaterialSerializer;
+
+MultiMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+MultiMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.MultiMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default MultiMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/ParticleBasicMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/ParticleBasicMaterialSerializer.js
new file mode 100644
index 00000000..3ab47f85
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/ParticleBasicMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * ParticleBasicMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ParticleBasicMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+ParticleBasicMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+ParticleBasicMaterialSerializer.prototype.constructor = ParticleBasicMaterialSerializer;
+
+ParticleBasicMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+ParticleBasicMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ParticleBasicMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ParticleBasicMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/ParticleSystemMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/ParticleSystemMaterialSerializer.js
new file mode 100644
index 00000000..499540ed
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/ParticleSystemMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * ParticleSystemMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ParticleSystemMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+ParticleSystemMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+ParticleSystemMaterialSerializer.prototype.constructor = ParticleSystemMaterialSerializer;
+
+ParticleSystemMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+ParticleSystemMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ParticleSystemMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ParticleSystemMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/PointCloudMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/PointCloudMaterialSerializer.js
new file mode 100644
index 00000000..ddf4f133
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/PointCloudMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * PointCloudMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PointCloudMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+PointCloudMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+PointCloudMaterialSerializer.prototype.constructor = PointCloudMaterialSerializer;
+
+PointCloudMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+PointCloudMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PointCloudMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default PointCloudMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/PointsMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/PointsMaterialSerializer.js
new file mode 100644
index 00000000..1ceb8536
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/PointsMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * PointsMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PointsMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+PointsMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+PointsMaterialSerializer.prototype.constructor = PointsMaterialSerializer;
+
+PointsMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+PointsMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.PointsMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default PointsMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/RawShaderMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/RawShaderMaterialSerializer.js
new file mode 100644
index 00000000..23c92c97
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/RawShaderMaterialSerializer.js
@@ -0,0 +1,70 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * RawShaderMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function RawShaderMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+RawShaderMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+RawShaderMaterialSerializer.prototype.constructor = RawShaderMaterialSerializer;
+
+RawShaderMaterialSerializer.prototype.toJSON = function (obj) {
+ var json = MaterialSerializer.prototype.toJSON.call(this, obj);
+
+ json.defines = obj.defines;
+
+ json.uniforms = {};
+
+ for (var i in obj.uniforms) {
+ var uniform = obj.uniforms[i];
+ if (uniform.value instanceof THREE.Color) {
+ json.uniforms[i] = {
+ type: 'color',
+ value: uniform.value
+ };
+ } else {
+ json.uniforms[i] = {
+ value: uniform.value
+ };
+ }
+ }
+
+ json.vertexShader = obj.vertexShader;
+ json.fragmentShader = obj.fragmentShader;
+
+ return json;
+};
+
+RawShaderMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.RawShaderMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.defines = json.defines;
+
+ obj.uniforms = {};
+
+ for (var i in json.uniforms) {
+ var uniform = json.uniforms[i];
+ if (uniform.type === 'color') {
+ obj.uniforms[i] = {
+ value: new THREE.Color(uniform.value)
+ };
+ } else {
+ obj.uniforms[i] = {
+ value: uniform.value
+ };
+ }
+ }
+
+ obj.vertexShader = json.vertexShader;
+ obj.fragmentShader = json.fragmentShader;
+
+ return obj;
+};
+
+export default RawShaderMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/ShaderMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/ShaderMaterialSerializer.js
new file mode 100644
index 00000000..e14754ae
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/ShaderMaterialSerializer.js
@@ -0,0 +1,70 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * ShaderMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ShaderMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+ShaderMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+ShaderMaterialSerializer.prototype.constructor = ShaderMaterialSerializer;
+
+ShaderMaterialSerializer.prototype.toJSON = function (obj) {
+ var json = MaterialSerializer.prototype.toJSON.call(this, obj);
+
+ json.defines = obj.defines;
+
+ json.uniforms = {};
+
+ for (var i in obj.uniforms) {
+ var uniform = obj.uniforms[i];
+ if (uniform.value instanceof THREE.Color) {
+ json.uniforms[i] = {
+ type: 'color',
+ value: uniform.value
+ };
+ } else {
+ json.uniforms[i] = {
+ value: uniform.value
+ };
+ }
+ }
+
+ json.vertexShader = obj.vertexShader;
+ json.fragmentShader = obj.fragmentShader;
+
+ return json;
+};
+
+ShaderMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ShaderMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.defines = json.defines;
+
+ obj.uniforms = {};
+
+ for (var i in json.uniforms) {
+ var uniform = json.uniforms[i];
+ if (uniform.type === 'color') {
+ obj.uniforms[i] = {
+ value: new THREE.Color(uniform.value)
+ };
+ } else {
+ obj.uniforms[i] = {
+ value: uniform.value
+ };
+ }
+ }
+
+ obj.vertexShader = json.vertexShader;
+ obj.fragmentShader = json.fragmentShader;
+
+ return obj;
+};
+
+export default ShaderMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/ShadowMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/ShadowMaterialSerializer.js
new file mode 100644
index 00000000..d6f44549
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/ShadowMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * ShadowMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ShadowMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+ShadowMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+ShadowMaterialSerializer.prototype.constructor = ShadowMaterialSerializer;
+
+ShadowMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+ShadowMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.ShadowMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default ShadowMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/SpriteCanvasMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/SpriteCanvasMaterialSerializer.js
new file mode 100644
index 00000000..b617665d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/SpriteCanvasMaterialSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * SpriteCanvasMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SpriteCanvasMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+SpriteCanvasMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+SpriteCanvasMaterialSerializer.prototype.constructor = SpriteCanvasMaterialSerializer;
+
+SpriteCanvasMaterialSerializer.prototype.toJSON = function (obj) {
+ return MaterialSerializer.prototype.toJSON.call(this, obj);
+};
+
+SpriteCanvasMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.SpriteCanvasMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default SpriteCanvasMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/material/SpriteMaterialSerializer.js b/ShadowEditor.Core/src/serialization/material/SpriteMaterialSerializer.js
new file mode 100644
index 00000000..fdcd8a49
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/material/SpriteMaterialSerializer.js
@@ -0,0 +1,29 @@
+import BaseSerializer from '../BaseSerializer';
+import MaterialSerializer from './MaterialSerializer';
+
+/**
+ * SpriteMaterialSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SpriteMaterialSerializer() {
+ BaseSerializer.call(this);
+}
+
+SpriteMaterialSerializer.prototype = Object.create(BaseSerializer.prototype);
+SpriteMaterialSerializer.prototype.constructor = SpriteMaterialSerializer;
+
+SpriteMaterialSerializer.prototype.toJSON = function (obj) {
+ var json = MaterialSerializer.prototype.toJSON.call(this, obj);
+ json.isSpriteMaterial = true;
+ return json;
+};
+
+SpriteMaterialSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.SpriteMaterial() : parent;
+
+ MaterialSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default SpriteMaterialSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/FireSerializer.js b/ShadowEditor.Core/src/serialization/objects/FireSerializer.js
new file mode 100644
index 00000000..9a948b42
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/FireSerializer.js
@@ -0,0 +1,39 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+import Fire from '../../object/component/Fire';
+
+/**
+ * FireSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function FireSerializer() {
+ BaseSerializer.call(this);
+}
+
+FireSerializer.prototype = Object.create(BaseSerializer.prototype);
+FireSerializer.prototype.constructor = FireSerializer;
+
+FireSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ delete json.userData.fire;
+
+ return json;
+};
+
+FireSerializer.prototype.fromJSON = function (json, parent, camera) {
+ var fire = new Fire(camera, {
+ width: json.userData.width,
+ height: json.userData.height,
+ depth: json.userData.depth,
+ sliceSpacing: json.userData.sliceSpacing
+ });
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, fire);
+
+ fire.userData.fire.update(0);
+
+ return fire;
+};
+
+export default FireSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/ParticleEmitterSerializer.js b/ShadowEditor.Core/src/serialization/objects/ParticleEmitterSerializer.js
new file mode 100644
index 00000000..24488ec8
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/ParticleEmitterSerializer.js
@@ -0,0 +1,148 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+import TexturesSerializer from '../texture/TexturesSerializer';
+import ParticleEmitter from '../../object/component/ParticleEmitter';
+
+/**
+ * ParticleEmitterSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ParticleEmitterSerializer() {
+ BaseSerializer.call(this);
+}
+
+ParticleEmitterSerializer.prototype = Object.create(BaseSerializer.prototype);
+ParticleEmitterSerializer.prototype.constructor = ParticleEmitterSerializer;
+
+ParticleEmitterSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ json.children.length = 0;
+
+ var group = json.userData.group;
+ var emitter = json.userData.emitter;
+
+ json.userData.group = {
+ texture: (new TexturesSerializer()).toJSON(group.texture),
+ maxParticleCount: group.maxParticleCount
+ };
+
+ json.userData.emitter = {
+ position: {
+ value: {
+ x: emitter.position.value.x,
+ y: emitter.position.value.y,
+ z: emitter.position.value.z
+ },
+ spread: {
+ x: emitter.position.spread.x,
+ y: emitter.position.spread.y,
+ z: emitter.position.spread.z
+ }
+ },
+ velocity: {
+ value: {
+ x: emitter.velocity.value.x,
+ y: emitter.velocity.value.y,
+ z: emitter.velocity.value.z
+ },
+ spread: {
+ x: emitter.velocity.spread.x,
+ y: emitter.velocity.spread.y,
+ z: emitter.velocity.spread.z
+ }
+ },
+ acceleration: {
+ value: {
+ x: emitter.acceleration.value.x,
+ y: emitter.acceleration.value.y,
+ z: emitter.acceleration.value.z
+ },
+ spread: {
+ x: emitter.acceleration.spread.x,
+ y: emitter.acceleration.spread.y,
+ z: emitter.acceleration.spread.z
+ }
+ },
+ color: {
+ value: [
+ emitter.color.value[0].getHex(),
+ emitter.color.value[1].getHex(),
+ emitter.color.value[2].getHex(),
+ emitter.color.value[3].getHex()
+ ]
+ },
+ size: {
+ value: emitter.size.value,
+ spread: emitter.size.spread
+ },
+ particleCount: emitter.particleCount,
+ maxAge: {
+ value: emitter.maxAge.value,
+ spread: emitter.maxAge.spread
+ }
+ };
+
+ return json;
+};
+
+ParticleEmitterSerializer.prototype.fromJSON = function (json) {
+ var groupJson = json.userData.group;
+ var emitterJson = json.userData.emitter;
+
+ var group = new SPE.Group({
+ texture: {
+ value: (new TexturesSerializer()).fromJSON(groupJson.texture)
+ },
+ maxParticleCount: groupJson.maxParticleCount
+ });
+
+ var emitter = new SPE.Emitter({
+ maxAge: {
+ value: emitterJson.maxAge.value
+ },
+ position: {
+ value: new THREE.Vector3().copy(emitterJson.position.value),
+ spread: new THREE.Vector3().copy(emitterJson.position.spread)
+ },
+
+ acceleration: {
+ value: new THREE.Vector3().copy(emitterJson.acceleration.value),
+ spread: new THREE.Vector3().copy(emitterJson.acceleration.spread)
+ },
+
+ velocity: {
+ value: new THREE.Vector3().copy(emitterJson.velocity.value),
+ spread: new THREE.Vector3().copy(emitterJson.velocity.spread)
+ },
+
+ color: {
+ value: [
+ new THREE.Color(emitterJson.color.value[0]),
+ new THREE.Color(emitterJson.color.value[1]),
+ new THREE.Color(emitterJson.color.value[2]),
+ new THREE.Color(emitterJson.color.value[3])
+ ]
+ },
+
+ size: {
+ value: emitterJson.size.value.slice(),
+ spread: emitterJson.size.spread.slice()
+ },
+
+ particleCount: emitterJson.particleCount
+ });
+
+ var obj = new ParticleEmitter(group, emitter);
+
+ delete json.userData.group;
+ delete json.userData.emitter;
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.userData.group.tick(0);
+
+ return obj;
+};
+
+export default ParticleEmitterSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/PerlinTerrainSerializer.js b/ShadowEditor.Core/src/serialization/objects/PerlinTerrainSerializer.js
new file mode 100644
index 00000000..efdca1c3
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/PerlinTerrainSerializer.js
@@ -0,0 +1,36 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+import PerlinTerrain from '../../object/terrain/PerlinTerrain';
+
+/**
+ * PerlinTerrainSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function PerlinTerrainSerializer() {
+ BaseSerializer.call(this);
+}
+
+PerlinTerrainSerializer.prototype = Object.create(BaseSerializer.prototype);
+PerlinTerrainSerializer.prototype.constructor = PerlinTerrainSerializer;
+
+PerlinTerrainSerializer.prototype.toJSON = function (obj) {
+ var json = Object3DSerializer.prototype.toJSON.call(this, obj);
+
+ return json;
+};
+
+PerlinTerrainSerializer.prototype.fromJSON = function (json, parent) {
+ var terrain = new PerlinTerrain(
+ json.userData.width,
+ json.userData.depth,
+ json.userData.widthSegments,
+ json.userData.depthSegments,
+ json.userData.quality,
+ );
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, terrain);
+
+ return terrain;
+};
+
+export default PerlinTerrainSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/ReflectorSerializer.js b/ShadowEditor.Core/src/serialization/objects/ReflectorSerializer.js
new file mode 100644
index 00000000..2a76b936
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/ReflectorSerializer.js
@@ -0,0 +1,45 @@
+import BaseSerializer from '../BaseSerializer';
+import MeshSerializer from '../core/MeshSerializer';
+import GeometriesSerializer from '../geometry/GeometriesSerializer';
+
+/**
+ * ReflectorSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function ReflectorSerializer() {
+ BaseSerializer.call(this);
+}
+
+ReflectorSerializer.prototype = Object.create(BaseSerializer.prototype);
+ReflectorSerializer.prototype.constructor = ReflectorSerializer;
+
+ReflectorSerializer.prototype.toJSON = function (obj) {
+ var json = MeshSerializer.prototype.toJSON.call(this, obj);
+
+ if (json.userData.mesh) {
+ json.userData.mesh = (new MeshSerializer()).toJSON(json.userData.mesh);
+ }
+
+ return json;
+};
+
+ReflectorSerializer.prototype.fromJSON = function (json) {
+ var geometry = (new GeometriesSerializer()).fromJSON(json.geometry);
+ var obj = new THREE.Reflector(geometry, {
+ color: json.userData.color,
+ textureWidth: parseInt(json.userData.size),
+ textureHeight: parseInt(json.userData.size),
+ clipBias: json.userData.clipBias,
+ recursion: json.userData.recursion ? 1 : 0
+ });
+
+ MeshSerializer.prototype.fromJSON.call(this, json, obj);
+
+ if (obj.userData.mesh) {
+ obj.userData.mesh = (new MeshSerializer()).fromJSON(obj.userData.mesh);
+ }
+
+ return obj;
+};
+
+export default ReflectorSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/SkySerializer.js b/ShadowEditor.Core/src/serialization/objects/SkySerializer.js
new file mode 100644
index 00000000..c7ffc97a
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/SkySerializer.js
@@ -0,0 +1,28 @@
+import BaseSerializer from '../BaseSerializer';
+import Object3DSerializer from '../core/Object3DSerializer';
+import Sky from '../../object/component/Sky';
+
+/**
+ * SkySerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SkySerializer() {
+ BaseSerializer.call(this);
+}
+
+SkySerializer.prototype = Object.create(BaseSerializer.prototype);
+SkySerializer.prototype.constructor = SkySerializer;
+
+SkySerializer.prototype.toJSON = function (obj) {
+ return Object3DSerializer.prototype.toJSON.call(this, obj);
+};
+
+SkySerializer.prototype.fromJSON = function (json, parent, camera) {
+ var obj = new Sky(json);
+
+ Object3DSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default SkySerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/objects/SmokeSerializer.js b/ShadowEditor.Core/src/serialization/objects/SmokeSerializer.js
new file mode 100644
index 00000000..f8f49f51
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/objects/SmokeSerializer.js
@@ -0,0 +1,32 @@
+import BaseSerializer from '../BaseSerializer';
+import MeshSerializer from '../core/MeshSerializer';
+import Smoke from '../../object/component/Smoke';
+
+/**
+ * SmokeSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function SmokeSerializer() {
+ BaseSerializer.call(this);
+}
+
+SmokeSerializer.prototype = Object.create(BaseSerializer.prototype);
+SmokeSerializer.prototype.constructor = SmokeSerializer;
+
+SmokeSerializer.prototype.toJSON = function (obj) {
+ var json = MeshSerializer.prototype.toJSON.call(this, obj);
+
+ return json;
+};
+
+SmokeSerializer.prototype.fromJSON = function (json, parent, camera, renderer) {
+ var obj = parent || new Smoke(camera, renderer);
+
+ MeshSerializer.prototype.fromJSON.call(this, json, obj);
+
+ obj.update(0);
+
+ return obj;
+};
+
+export default SmokeSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/CanvasTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/CanvasTextureSerializer.js
new file mode 100644
index 00000000..13512086
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/CanvasTextureSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+
+/**
+ * CanvasTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CanvasTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+CanvasTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+CanvasTextureSerializer.prototype.constructor = CanvasTextureSerializer;
+
+CanvasTextureSerializer.prototype.toJSON = function (obj) {
+ return TextureSerializer.prototype.toJSON.call(this, obj);
+};
+
+CanvasTextureSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.CanvasTexture() : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default CanvasTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/CompressedTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/CompressedTextureSerializer.js
new file mode 100644
index 00000000..262b1528
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/CompressedTextureSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+
+/**
+ * CompressedTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CompressedTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+CompressedTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+CompressedTextureSerializer.prototype.constructor = CompressedTextureSerializer;
+
+CompressedTextureSerializer.prototype.toJSON = function (obj) {
+ return TextureSerializer.prototype.toJSON.call(this, obj);
+};
+
+CompressedTextureSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.CompressedTexture() : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default CompressedTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/CubeTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/CubeTextureSerializer.js
new file mode 100644
index 00000000..a106f3ef
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/CubeTextureSerializer.js
@@ -0,0 +1,61 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+import ImageUtils from '../../utils/ImageUtils';
+
+/**
+ * CubeTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function CubeTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+CubeTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+CubeTextureSerializer.prototype.constructor = CubeTextureSerializer;
+
+CubeTextureSerializer.prototype.toJSON = function (obj) {
+ var json = TextureSerializer.prototype.toJSON.call(this, obj);
+
+ json.image = [];
+
+ obj.image.forEach(n => {
+ json.image.push({
+ tagName: 'img',
+ src: n.src,
+ width: n.width,
+ height: n.height
+ });
+ });
+
+ return json;
+};
+
+CubeTextureSerializer.prototype.fromJSON = function (json, parent) {
+ // 用一个像素的图片初始化CubeTexture,避免图片载入前的警告信息。
+ var img = ImageUtils.onePixelCanvas();
+ var obj = parent === undefined ? new THREE.CubeTexture([img, img, img, img, img, img]) : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ if (Array.isArray(json.image)) {
+ var promises = json.image.map(n => {
+ return new Promise(resolve => {
+ var img = document.createElement('img');
+ img.src = n.src;
+ img.width = n.width;
+ img.height = n.height;
+ img.onload = () => {
+ resolve(img);
+ };
+ });
+ });
+ Promise.all(promises).then(imgs => {
+ obj.image = imgs;
+ obj.needsUpdate = true;
+ });
+ }
+
+ return obj;
+};
+
+export default CubeTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/DataTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/DataTextureSerializer.js
new file mode 100644
index 00000000..5da79780
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/DataTextureSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+
+/**
+ * DataTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function DataTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+DataTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+DataTextureSerializer.prototype.constructor = DataTextureSerializer;
+
+DataTextureSerializer.prototype.toJSON = function (obj) {
+ return TextureSerializer.prototype.toJSON.call(this, obj);
+};
+
+DataTextureSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.DataTexture() : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default DataTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/DepthTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/DepthTextureSerializer.js
new file mode 100644
index 00000000..95cab0bd
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/DepthTextureSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+
+/**
+ * DepthTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function DepthTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+DepthTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+DepthTextureSerializer.prototype.constructor = DepthTextureSerializer;
+
+DepthTextureSerializer.prototype.toJSON = function (obj) {
+ return TextureSerializer.prototype.toJSON.call(this, obj);
+};
+
+DepthTextureSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.DataTexture() : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default DepthTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/TextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/TextureSerializer.js
new file mode 100644
index 00000000..c5dadcb7
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/TextureSerializer.js
@@ -0,0 +1,130 @@
+import BaseSerializer from '../BaseSerializer';
+import ImageUtils from '../../utils/ImageUtils';
+
+/**
+ * TextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+TextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+TextureSerializer.prototype.constructor = TextureSerializer;
+
+TextureSerializer.prototype.toJSON = function (obj) {
+ var json = BaseSerializer.prototype.toJSON.call(this, obj);
+
+ json.anisotropy = obj.anisotropy;
+ json.center = obj.center;
+ json.encoding = obj.encoding;
+ json.flipY = obj.flipY;
+ json.format = obj.format;
+ json.generateMipmaps = obj.generateMipmaps;
+
+ // 说明:立体贴图obj.image是一个图片数组。
+ if (obj.image && !Array.isArray(obj.image) && obj.image.tagName.toLowerCase() === 'img') { // 图片
+ json.image = {
+ tagName: 'img',
+ src: obj.image.src,
+ width: obj.image.width,
+ height: obj.image.height
+ };
+ } else if (obj.image && !Array.isArray(obj.image) && obj.image.tagName.toLowerCase() === 'canvas') { // 画布
+ json.image = {
+ tagName: 'canvas',
+ src: obj.image.toDataURL(),
+ width: obj.image.width,
+ height: obj.image.height
+ };
+ } else {
+ json.image = null;
+ }
+
+ json.magFilter = obj.magFilter;
+ json.mapping = obj.mapping;
+ json.matrix = obj.matrix;
+ json.matrixAutoUpdate = obj.matrixAutoUpdate;
+ json.minFilter = obj.minFilter;
+ json.mipmaps = obj.mipmaps;
+ json.name = obj.name;
+ json.offset = obj.offset;
+ json.premultiplyAlpha = obj.premultiplyAlpha;
+ json.repeat = obj.repeat;
+ json.rotation = obj.rotation;
+ json.type = obj.type;
+ json.unpackAlignment = obj.unpackAlignment;
+ json.uuid = obj.uuid;
+ json.version = obj.version;
+ json.wrapS = obj.wrapS;
+ json.wrapT = obj.wrapT;
+ json.isTexture = obj.isTexture;
+ json.needsUpdate = obj.needsUpdate;
+
+ return json;
+};
+
+TextureSerializer.prototype.fromJSON = function (json, parent) {
+ // 用一个像素的图片初始化Texture,避免图片载入前的警告信息。
+ var img = ImageUtils.onePixelCanvas();
+ var obj = parent === undefined ? new THREE.Texture(img) : parent;
+
+ obj.anisotropy = json.anisotropy;
+ obj.center.copy(json.center);
+ obj.encoding = json.encoding;
+ obj.flipY = json.flipY;
+ obj.format = json.format;
+ obj.generateMipmaps = json.generateMipmaps;
+
+ if (json.image && !Array.isArray(json.image) && json.image.tagName === 'img') { // 图片
+ var img = document.createElement('img');
+ img.src = json.image.src;
+ img.width = json.image.width;
+ img.height = json.image.height;
+ img.onload = function () {
+ obj.image = img;
+ obj.needsUpdate = true;
+ };
+ } else if (json.image && !Array.isArray(obj.image) && json.image.tagName === 'canvas') { // 画布
+ var canvas = document.createElement('canvas');
+ canvas.width = 256;
+ canvas.height = 256;
+ var ctx = canvas.getContext('2d');
+
+ var img = document.createElement('img');
+ img.src = json.image.src;
+ img.onload = function () {
+ canvas.width = img.width;
+ canvas.height = img.height;
+ ctx.drawImage(img, 0, 0);
+
+ obj.needsUpdate = true;
+ };
+
+ obj.image = canvas;
+ }
+
+ obj.magFilter = json.magFilter;
+ obj.mapping = json.mapping;
+ obj.matrix.copy(json.matrix);
+ obj.matrixAutoUpdate = json.matrixAutoUpdate;
+ obj.minFilter = json.minFilter;
+ obj.mipmaps = json.mipmaps;
+ obj.name = json.name;
+ obj.offset.copy(json.offset);
+ obj.premultiplyAlpha = json.premultiplyAlpha;
+ obj.repeat.copy(json.repeat);
+ obj.rotation = json.rotation;
+ obj.type = json.type;
+ obj.unpackAlignment = json.unpackAlignment;
+ obj.uuid = json.uuid;
+ obj.version = json.version;
+ obj.wrapS = json.wrapS;
+ obj.wrapT = json.wrapT;
+ obj.isTexture = json.isTexture;
+ obj.needsUpdate = true;
+
+ return obj;
+};
+
+export default TextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/TexturesSerializer.js b/ShadowEditor.Core/src/serialization/texture/TexturesSerializer.js
new file mode 100644
index 00000000..7546167d
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/TexturesSerializer.js
@@ -0,0 +1,56 @@
+import BaseSerializer from '../BaseSerializer';
+
+import TextureSerializer from './TextureSerializer';
+import CanvasTextureSerializer from './CanvasTextureSerializer';
+import CompressedTextureSerializer from './CompressedTextureSerializer';
+import CubeTextureSerializer from './CubeTextureSerializer';
+import DataTextureSerializer from './DataTextureSerializer';
+import DepthTextureSerializer from './DepthTextureSerializer';
+import VideoTextureSerializer from './VideoTextureSerializer';
+
+var Serializers = {
+ 'CanvasTexture': CanvasTextureSerializer,
+ 'CompressedTexture': CompressedTextureSerializer,
+ 'CubeTexture': CubeTextureSerializer,
+ 'DataTexture': DataTextureSerializer,
+ 'DepthTexture': DepthTextureSerializer,
+ 'VideoTexture': VideoTextureSerializer,
+ 'Texture': TextureSerializer
+};
+
+/**
+ * TexturesSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function TexturesSerializer() {
+ BaseSerializer.call(this);
+}
+
+TexturesSerializer.prototype = Object.create(BaseSerializer.prototype);
+TexturesSerializer.prototype.constructor = TexturesSerializer;
+
+TexturesSerializer.prototype.toJSON = function (obj) {
+ var serializer = Serializers[obj.constructor.name];
+
+ if (serializer === undefined) {
+ console.warn(`TexturesSerializer: 无法序列化${obj.type}。`);
+ return null;
+ }
+
+ return (new serializer()).toJSON(obj);
+};
+
+TexturesSerializer.prototype.fromJSON = function (json, parent) {
+ var generator = json.metadata.generator;
+
+ var serializer = Serializers[generator.replace('Serializer', '')];
+
+ if (serializer === undefined) {
+ console.warn(`TexturesSerializer: 不存在 ${generator} 的反序列化器`);
+ return null;
+ }
+
+ return (new serializer()).fromJSON(json, parent);
+};
+
+export default TexturesSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/serialization/texture/VideoTextureSerializer.js b/ShadowEditor.Core/src/serialization/texture/VideoTextureSerializer.js
new file mode 100644
index 00000000..c300dc1c
--- /dev/null
+++ b/ShadowEditor.Core/src/serialization/texture/VideoTextureSerializer.js
@@ -0,0 +1,27 @@
+import BaseSerializer from '../BaseSerializer';
+import TextureSerializer from './TextureSerializer';
+
+/**
+ * VideoTextureSerializer
+ * @author tengge / https://github.com/tengge1
+ */
+function VideoTextureSerializer() {
+ BaseSerializer.call(this);
+}
+
+VideoTextureSerializer.prototype = Object.create(BaseSerializer.prototype);
+VideoTextureSerializer.prototype.constructor = VideoTextureSerializer;
+
+VideoTextureSerializer.prototype.toJSON = function (obj) {
+ return TextureSerializer.prototype.toJSON.call(this, obj);
+};
+
+VideoTextureSerializer.prototype.fromJSON = function (json, parent) {
+ var obj = parent === undefined ? new THREE.VideoTexture() : parent;
+
+ TextureSerializer.prototype.fromJSON.call(this, json, obj);
+
+ return obj;
+};
+
+export default VideoTextureSerializer;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/third_party.js b/ShadowEditor.Core/src/third_party.js
new file mode 100644
index 00000000..7494f78e
--- /dev/null
+++ b/ShadowEditor.Core/src/third_party.js
@@ -0,0 +1,4 @@
+import './polyfills';
+
+export { dispatch } from 'd3-dispatch';
+export { Control, UI } from '../../ShadowEditor.UI/src/index';
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/ui/ImageListWindow.js b/ShadowEditor.Core/src/ui/ImageListWindow.js
new file mode 100644
index 00000000..0a4d63fa
--- /dev/null
+++ b/ShadowEditor.Core/src/ui/ImageListWindow.js
@@ -0,0 +1,227 @@
+import { Control, UI } from '../third_party';
+import UploadUtils from '../utils/UploadUtils';
+
+/**
+ * 图片列表窗口
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function ImageListWindow(options) {
+ Control.call(this, options);
+
+ this.title = options.title || '图片列表';
+ this.width = options.width || '700px';
+ this.height = options.height || '500px';
+
+ this.imageIcon = options.imageIcon || null; // 图片列表默认图标
+ this.nameField = options.nameField || 'Name'; // 名称字段,用于显示图片标题,按文字搜索
+ this.firstPinYinField = options.firstPinYinField || 'FirstPinYin'; // 拼音首字母字段,用于搜索
+ this.totalPinYinField = options.totalPinYinField || 'TotalPinYin'; // 全拼字段,用于搜索
+ this.cornerTextField = options.cornerTextField || null; // 显示在左上角文字字段
+ this.imageField = options.imageField || 'Thumbnail'; // 图片字段,用于显示缩略图
+ this.uploadUrl = options.uploadUrl || '/api/Upload/Upload';
+ this.preImageUrl = options.preImageUrl || '/'; // 缩略图url前缀,一般是服务端url
+ this.showUploadButton = options.showUploadButton || false; // 是否显示上传按钮
+
+ this.settingPanel = options.settingPanel || null; // 设置面板,格式:{ xtype: 'div', children: [ ... ] }
+
+ this.beforeUpdateList = options.beforeUpdateList || this.beforeUpdateList; // 图片列表刷新前调用,返回Promise,resolve(data)。
+ this.onUpload = options.onUpload || this.onUpload; // 上传成功回调
+ this.onClick = options.onClick || this.onClick; // 点击图片列表
+ this.onEdit = options.onEdit || this.onEdit; // 编辑图片列表
+ this.onDelete = options.onDelete || this.onDelete; // 删除图片列表
+
+ this.data = [];
+ this.keyword = '';
+
+ this.input = document.createElement('input');
+ this.input.id = `file_${this.id}`;
+ this.input.type = 'file';
+ this.input.style.display = 'none';
+ this.input.addEventListener('change', this.onChange.bind(this));
+ document.body.appendChild(this.input);
+}
+
+ImageListWindow.prototype = Object.create(Control.prototype);
+ImageListWindow.prototype.constructor = ImageListWindow;
+
+ImageListWindow.prototype.render = function () {
+ var data = {
+ xtype: 'window',
+ id: 'window',
+ scope: this.id,
+ parent: this.parent,
+ title: this.title,
+ width: this.width,
+ height: this.height,
+ bodyStyle: {
+ paddingTop: 0,
+ display: 'flex',
+ flexDirection: 'row'
+ },
+ shade: false,
+ children: [{
+ xtype: 'div',
+ style: {
+ flex: 1
+ },
+ children: [{
+ xtype: 'row',
+ style: {
+ position: 'sticky',
+ top: '0',
+ padding: '2px',
+ backgroundColor: '#eee',
+ borderBottom: '1px solid #ddd',
+ zIndex: 100,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-start'
+ },
+ children: [{
+ xtype: 'searchfield',
+ id: 'search',
+ scope: this.id,
+ showSearchButton: false,
+ showResetButton: true,
+ onInput: this.onInputSearchField.bind(this)
+ }]
+ }, {
+ xtype: 'row',
+ children: [{
+ xtype: 'imagelist',
+ id: 'windowImages',
+ scope: this.id,
+ style: {
+ width: '100%',
+ height: '100%',
+ },
+ onClick: this.onClickImage.bind(this)
+ }]
+ }]
+ }]
+ };
+
+ if (this.showUploadButton) {
+ data.children[0].children[0].children.splice(0, 0, {
+ xtype: 'button',
+ text: '上传',
+ onClick: this.onClickUpload.bind(this)
+ });
+ data.children[0].children[0].children[1].style = {
+ position: 'absolute',
+ right: 0,
+ marginRight: '8px'
+ };
+ }
+
+ if (this.settingPanel) {
+ data.children.push(this.settingPanel);
+ }
+
+ var container = UI.create(data);
+ container.render();
+};
+
+ImageListWindow.prototype.show = function () {
+ UI.get('window', this.id).show();
+ this.update();
+};
+
+ImageListWindow.prototype.hide = function () {
+ UI.get('window', this.id).hide();
+};
+
+ImageListWindow.prototype.update = function () {
+ this.keyword = '';
+
+ if (typeof (this.beforeUpdateList) === 'function') {
+ this.beforeUpdateList().then((data) => {
+ this.data = data;
+ this.onSearch(this.keyword);
+ });
+ }
+};
+
+ImageListWindow.prototype.onClickUpload = function () {
+ this.input.value = null;
+ this.input.click();
+};
+
+ImageListWindow.prototype.onChange = function () {
+ UploadUtils.upload(`file_${this.id}`, this.uploadUrl, event => {
+ if (event.target.status === 200) {
+ var response = event.target.response;
+ var obj = JSON.parse(response);
+ this.onUpload(obj);
+ } else {
+ UI.msg('上传失败!');
+ }
+ }, () => {
+ UI.msg('上传失败!');
+ });
+};
+
+ImageListWindow.prototype.onSearch = function (name) {
+ if (name.trim() === '') {
+ this.renderImages(this.data);
+ return;
+ }
+
+ name = name.toLowerCase();
+
+ var models = this.data.filter((n) => {
+ return n[this.nameField].indexOf(name) > -1 ||
+ n[this.firstPinYinField].indexOf(name) > -1 ||
+ n[this.totalPinYinField].indexOf(name) > -1;
+ });
+ this.renderImages(models);
+};
+
+ImageListWindow.prototype.onInputSearchField = function () {
+ var search = UI.get('search', this.id);
+ this.onSearch(search.getValue());
+};
+
+ImageListWindow.prototype.renderImages = function (models) {
+ var images = UI.get('windowImages', this.id);
+ images.clear();
+
+ images.children = models.map(n => {
+ return {
+ xtype: 'image',
+ src: (n[this.imageField] === undefined || n[this.imageField] === null || n[this.imageField].trim() === '') ? null : (this.preImageUrl + n[this.imageField]),
+ title: n[this.nameField],
+ data: n,
+ icon: this.imageIcon,
+ cornerText: this.cornerTextField === null ? null : n[this.cornerTextField],
+ style: {
+ backgroundColor: '#eee'
+ }
+ };
+ });;
+
+ images.render();
+};
+
+ImageListWindow.prototype.onClickImage = function (event, index, btn, control) {
+ var data = control.children[index].data;
+
+ if (btn === 'edit') {
+ if (typeof (this.onEdit) === 'function') {
+ this.onEdit(data);
+ }
+ } else if (btn === 'delete') {
+ if (typeof (this.onDelete) === 'function') {
+ this.onDelete(data);
+ }
+ } else {
+ if (typeof (this.onClick) === 'function') {
+ this.onClick(data);
+ }
+ }
+};
+
+UI.addXType('imagelistwindow', ImageListWindow);
+
+export default ImageListWindow;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/ui/ImageUploader.js b/ShadowEditor.Core/src/ui/ImageUploader.js
new file mode 100644
index 00000000..f7f21c82
--- /dev/null
+++ b/ShadowEditor.Core/src/ui/ImageUploader.js
@@ -0,0 +1,76 @@
+import { Control, UI } from '../third_party';
+import UploadUtils from '../utils/UploadUtils';
+
+/**
+ * 图片上传控件
+ * @param {*} options
+ */
+function ImageUploader(options) {
+ Control.call(this, options);
+ options = options || {};
+
+ this.url = options.url || null;
+ this.server = options.server || '';
+
+ this.input = document.createElement('input');
+ this.input.id = `file_${this.id}`;
+ this.input.type = 'file';
+ this.input.style.display = 'none';
+ this.input.addEventListener('change', this.onChange.bind(this));
+ document.body.appendChild(this.input);
+}
+
+ImageUploader.prototype = Object.create(Control.prototype);
+ImageUploader.prototype.constructor = ImageUploader;
+
+ImageUploader.prototype.render = function () {
+ if (this.dom) {
+ this.dom.removeEventListener('click', this.onClick.bind(this));
+ this.parent.removeChild(this.dom);
+ }
+
+ if (this.url) {
+ this.dom = document.createElement('img');
+ this.dom.className = 'Uploader';
+ this.dom.src = this.server + this.url;
+ } else {
+ this.dom = document.createElement('div');
+ this.dom.className = 'NoImage';
+ this.dom.innerHTML = '暂无图片';
+ }
+ this.parent.appendChild(this.dom);
+ this.dom.addEventListener('click', this.onClick.bind(this));
+ this.input.value = null;
+};
+
+ImageUploader.prototype.onClick = function () {
+ this.input.click();
+};
+
+ImageUploader.prototype.getValue = function () {
+ return this.url;
+};
+
+ImageUploader.prototype.setValue = function (url) {
+ this.url = url;
+ this.render();
+};
+
+ImageUploader.prototype.onChange = function () {
+ UploadUtils.upload(`file_${this.id}`, `${this.server}/api/Upload/Upload`, event => {
+ if (event.target.status === 200) {
+ var response = event.target.response;
+ var obj = JSON.parse(response);
+ var url = obj.Data.url;
+ this.setValue(url);
+ } else {
+ UI.msg('图片上传失败!');
+ }
+ }, () => {
+ UI.msg('图片上传失败!');
+ });
+};
+
+UI.addXType('imageuploader', ImageUploader);
+
+export default ImageUploader;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/ui/Outliner.js b/ShadowEditor.Core/src/ui/Outliner.js
new file mode 100644
index 00000000..dc25882e
--- /dev/null
+++ b/ShadowEditor.Core/src/ui/Outliner.js
@@ -0,0 +1,236 @@
+import { Control, UI } from '../third_party';
+import MoveObjectCommand from '../command/MoveObjectCommand';
+
+/**
+ * 大纲控件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} options
+ */
+function Outliner(options) {
+ Control.call(this, options);
+ options = options || {};
+
+ this.onChange = options.onChange || null;
+ this.onDblClick = options.onDblClick || null;
+}
+
+Outliner.prototype = Object.create(Control.prototype);
+Outliner.prototype.constructor = Outliner;
+
+Outliner.prototype.render = function () {
+ this.dom = document.createElement('div');
+
+ this.dom.className = 'Outliner';
+ this.dom.tabIndex = 0; // keyup event is ignored without setting tabIndex
+
+ // Prevent native scroll behavior
+ this.dom.addEventListener('keydown', function (event) {
+ switch (event.keyCode) {
+ case 38: // up
+ case 40: // down
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ }
+
+ }, false);
+
+ var _this = this;
+
+ // Keybindings to support arrow navigation
+ this.dom.addEventListener('keyup', function (event) {
+ switch (event.keyCode) {
+ case 38: // up
+ _this.selectIndex(scope.selectedIndex - 1);
+ break;
+ case 40: // down
+ _this.selectIndex(scope.selectedIndex + 1);
+ break;
+ }
+
+ }, false);
+
+ this.parent.appendChild(this.dom);
+
+ if (this.onChange) {
+ this.dom.addEventListener('change', this.onChange.bind(this));
+ }
+
+ if (this.onDblClick) {
+ this.dom.addEventListener('dblclick', this.onDblClick.bind(this));
+ }
+
+ this.options = [];
+ this.selectedIndex = - 1;
+ this.selectedValue = null;
+};
+
+Outliner.prototype.selectIndex = function (index) {
+ if (index >= 0 && index < this.options.length) {
+ this.setValue(this.options[index].value);
+
+ var changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ this.dom.dispatchEvent(changeEvent);
+ }
+};
+
+Outliner.prototype.setOptions = function (options) {
+ var _this = this;
+
+ if (_this.editor === undefined) {
+ throw 'Outliner: editor is undefined.';
+ }
+
+ _this.scene = _this.editor.scene;
+
+ while (this.dom.children.length > 0) {
+ this.dom.removeChild(this.dom.firstChild);
+ }
+
+ function onClick() {
+ _this.setValue(this.value);
+
+ var changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ _this.dom.dispatchEvent(changeEvent);
+ }
+
+ // Drag
+ var currentDrag;
+
+ function onDrag(event) {
+ currentDrag = this;
+ }
+
+ function onDragStart(event) {
+ event.dataTransfer.setData('text', 'foo');
+ }
+
+ function onDragOver(event) {
+ if (this === currentDrag) {
+ return;
+ }
+
+ var area = event.offsetY / this.clientHeight;
+
+ if (area < 0.25) {
+ this.className = 'option dragTop';
+ } else if (area > 0.75) {
+ this.className = 'option dragBottom';
+ } else {
+ this.className = 'option drag';
+ }
+ }
+
+ function onDragLeave() {
+ if (this === currentDrag) {
+ return;
+ }
+
+ this.className = 'option';
+ }
+
+ function onDrop(event) {
+ if (this === currentDrag) {
+ return;
+ }
+
+ this.className = 'option';
+
+ var scene = _this.scene;
+ var object = scene.getObjectById(currentDrag.value);
+
+ var area = event.offsetY / this.clientHeight;
+
+ if (area < 0.25) {
+ var nextObject = scene.getObjectById(this.value);
+ moveObject(object, nextObject.parent, nextObject);
+ } else if (area > 0.75) {
+ var nextObject = scene.getObjectById(this.nextSibling.value);
+ moveObject(object, nextObject.parent, nextObject);
+ } else {
+ var parentObject = scene.getObjectById(this.value);
+ moveObject(object, parentObject);
+ }
+ }
+
+ function moveObject(object, newParent, nextObject) {
+ if (nextObject === null) nextObject = undefined;
+
+ var newParentIsChild = false;
+
+ object.traverse(function (child) {
+ if (child === newParent) newParentIsChild = true;
+ });
+
+ if (newParentIsChild) return;
+
+ _this.editor.execute(new MoveObjectCommand(object, newParent, nextObject));
+
+ var changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ _this.dom.dispatchEvent(changeEvent);
+ }
+
+ //
+ _this.options = [];
+
+ for (var i = 0; i < options.length; i++) {
+ var div = options[i];
+ div.className = 'option';
+ _this.dom.appendChild(div);
+
+ _this.options.push(div);
+
+ div.addEventListener('click', onClick, false);
+
+ if (div.draggable === true) {
+ div.addEventListener('drag', onDrag, false);
+ div.addEventListener('dragstart', onDragStart, false); // Firefox needs this
+
+ div.addEventListener('dragover', onDragOver, false);
+ div.addEventListener('dragleave', onDragLeave, false);
+ div.addEventListener('drop', onDrop, false);
+ }
+ }
+
+ return _this;
+};
+
+Outliner.prototype.getValue = function () {
+ return this.selectedValue;
+};
+
+Outliner.prototype.setValue = function (value) {
+ for (var i = 0; i < this.options.length; i++) {
+ var element = this.options[i];
+
+ if (element.value === value) {
+ element.classList.add('active');
+
+ // scroll into view
+ var y = element.offsetTop - this.dom.offsetTop;
+ var bottomY = y + element.offsetHeight;
+ var minScroll = bottomY - this.dom.offsetHeight;
+
+ if (this.dom.scrollTop > y) {
+ this.dom.scrollTop = y;
+ } else if (this.dom.scrollTop < minScroll) {
+ this.dom.scrollTop = minScroll;
+ }
+
+ this.selectedIndex = i;
+ } else {
+ element.classList.remove('active');
+ }
+ }
+
+ this.selectedValue = value;
+
+ return this;
+};
+
+UI.addXType('outliner', Outliner);
+
+export default Outliner;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/Ajax.js b/ShadowEditor.Core/src/utils/Ajax.js
new file mode 100644
index 00000000..23f5065c
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/Ajax.js
@@ -0,0 +1,119 @@
+import MIMETypeUtils from './MIMETypeUtils';
+
+/**
+ * ajax
+ * @author tengge / https://github.com/tengge1
+ * @param {*} params 参数
+ */
+function ajax(params) {
+ const url = params.url || '';
+ const method = params.method || 'GET';
+ const data = params.data || null;
+ const callback = params.callback || null;
+
+ const xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ var data = xhr.responseText;
+ typeof (callback) === 'function' && callback(data);
+ }
+ }
+
+ if (data === null) { // 不需要POST数据
+ xhr.send(null);
+ return;
+ }
+
+ // 判断是发送表单还是上传文件
+ // 由于API Controller只能序列化Content-Type为`application/x-www-form-urlencoded`的数据,所以发送表单和上传文件只能二选一。
+ // 否则报错:"No MediaTypeFormatter is available to read an object of type 'EditTextureModel' from content with media type 'multipart/form-data'.
+ var hasFile = false;
+
+ for (var name in data) {
+ if (data[name] instanceof Blob) {
+ hasFile = true;
+ break;
+ }
+ }
+
+ if (hasFile) { // 上传文件
+ var formData = new FormData();
+
+ for (var name in data) {
+ if (data[name] instanceof Blob) {
+ formData.append(name, data[name], `${data[name].name}.${MIMETypeUtils.getExtension(data[name].type)}`);
+ }
+ }
+
+ xhr.send(formData);
+ } else { // 发送表单
+ var bodies = [];
+ for (var name in data) {
+ bodies.push(name + '=' + encodeURIComponent(data[name]));
+ }
+
+ var body = bodies.join('&');
+ if (body.length) {
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ }
+
+ xhr.send(body);
+ }
+}
+
+/**
+ * get请求
+ * @param {*} url 地址
+ * @param {*} callback 回调函数
+ */
+function get(url, callback) {
+ ajax({
+ url: url,
+ callback: callback
+ });
+}
+
+/**
+ * get请求并解析json数据
+ * @param {*} url
+ * @param {*} callback
+ */
+function getJson(url, callback) {
+ ajax({
+ url: url,
+ callback: function (data) {
+ typeof (callback) === 'function' && callback(JSON.parse(data));
+ }
+ })
+}
+
+/**
+ * post请求
+ * @param {*} url 地址
+ * @param {*} data 数据
+ * @param {*} callback 回调函数
+ */
+function post(url, data, callback) {
+ const _data = typeof (data) === 'function' ? null : data;
+ const _callback = typeof (data) === 'function' ? data : callback;
+
+ ajax({
+ url: url,
+ method: 'POST',
+ data: _data,
+ callback: _callback
+ });
+}
+
+/**
+ * Ajax
+ */
+const Ajax = {
+ ajax: ajax,
+ get: get,
+ getJson: getJson,
+ post: post
+};
+
+export default Ajax;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/Converter.js b/ShadowEditor.Core/src/utils/Converter.js
new file mode 100644
index 00000000..84e94c3f
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/Converter.js
@@ -0,0 +1,172 @@
+/**
+ * canvas转DataURL
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} canvas 画布
+ * @param {*} type 图片类型 image/png或image/jpeg
+ * @param {*} quality jpeg图片质量
+ */
+function canvasToDataURL(canvas, type = 'image/png', quality = 0.8) {
+ if (type.toLowerCase() === 'image/png') {
+ return canvas.toDataURL(type);
+ } else {
+ return canvas.toDataURL(type, quality);
+ }
+}
+
+/**
+ * Blob转DataURL
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} blob Blob对象
+ */
+function blobToDataURL(blob) {
+ var reader = new FileReader();
+
+ return new Promise(resolve => {
+ reader.onload = e => {
+ resolve(e.target.result);
+ };
+ reader.readAsDataURL(blob);
+ });
+}
+
+/**
+ * 文件转DataURL
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} file 文件
+ */
+function fileToDataURL(file) {
+ return blobToDataURL(file);
+}
+
+/**
+ * DataURL转Blob
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} dataURL
+ */
+function dataURLToBlob(dataURL) {
+ var array = dataURL.split(',');
+ var mimeType = array[0].match(/:(.*?);/)[1];
+ var binaryString = atob(array[1]);
+ var length = binaryString.length;
+ var uint8Array = new Uint8Array(length);
+ while (length--) {
+ uint8Array[length] = binaryString.charCodeAt(length);
+ }
+ return new Blob([uint8Array], { type: mimeType });
+}
+
+/**
+ * DataURL转File
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} dataURL
+ * @param {*} filename 文件名
+ */
+function dataURLtoFile(dataURL, filename) {
+ var array = dataURL.split(',');
+ var mimeType = array[0].match(/:(.*?);/)[1];
+ var binaryString = atob(array[1]);
+ var length = binaryString.length;
+ var uint8Array = new Uint8Array(length);
+ while (length--) {
+ uint8Array[length] = binaryString.charCodeAt(length);
+ }
+ return new File([uint8Array], filename, { type: mimeType });
+}
+
+/**
+ * DataURL转图片
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} dataURL
+ */
+function dataURLToImage(dataURL) {
+ var image = new Image();
+
+ return new Promise(resolve => {
+ image.onload = () => {
+ image.onload = null;
+ image.onerror = null;
+ resolve(image);
+ };
+ image.onerror = () => {
+ image.onload = null;
+ image.onerror = null;
+ resolve(null);
+ };
+ image.src = dataURL;
+ });
+}
+
+/**
+ * Blob转图片
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} blob
+ */
+function BlobToImage(blob) {
+ return new Promise(resolve => {
+ blobToDataURL(blob).then(dataURL => {
+ dataURLToImage(dataURL).then(image => {
+ resolve(image);
+ });
+ });
+ });
+}
+
+/**
+ * 文件转图片
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} file 文件
+ */
+function FileToImage(file) {
+ return BlobToImage(file);
+}
+
+/**
+ * 图片转画布
+ * @author cuixiping / https://blog.csdn.net/cuixiping/article/details/45932793
+ * @param {*} image 图片
+ */
+function ImageToCanvas(image) {
+ var canvas = document.createElement('canvas');
+ canvas.width = image.width;
+ canvas.height = image.height;
+
+ var context = canvas.getContext('2d');
+ context.drawImage(image, 0, 0);
+
+ return canvas;
+}
+
+/**
+ * 画布转图片
+ * @param {*} canvas 画布
+ * @param {*} type 类型
+ * @param {*} quality jpeg图片质量
+ */
+function CanvasToImage(canvas, type = 'image/png', quality = 0.8) {
+ var image = new Image();
+ if (type === 'image/jpeg') {
+ image.src = canvas.toDataURL('image/jpeg', quality);
+ } else {
+ image.src = canvas.toDataURL('image/png');
+ }
+ return image;
+}
+
+/**
+ * 类型转换工具
+ * @author tengge / https://github.com/tengge1
+ */
+const Converter = {
+ canvasToDataURL: canvasToDataURL,
+ blobToDataURL: blobToDataURL,
+ fileToDataURL: fileToDataURL,
+ dataURLToBlob: dataURLToBlob,
+ dataURLtoFile: dataURLtoFile,
+ dataURLToImage: dataURLToImage,
+ BlobToImage: BlobToImage,
+ FileToImage: FileToImage,
+ imageToCanvas: ImageToCanvas,
+ canvasToImage: CanvasToImage,
+};
+
+export default Converter;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/CssUtils.js b/ShadowEditor.Core/src/utils/CssUtils.js
new file mode 100644
index 00000000..a74c6342
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/CssUtils.js
@@ -0,0 +1,22 @@
+/**
+ * 异步加载css文件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} url css文件url
+ */
+function loadCss(url) {
+ var head = document.getElementsByTagName('head')[0];
+ var link = document.createElement('link');
+ link.type = 'text/css';
+ link.rel = 'stylesheet';
+ link.href = url;
+ head.appendChild(link);
+};
+
+/**
+ * css工具类
+ */
+const CssUtils = {
+ load: loadCss
+};
+
+export default CssUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/GeometryUtils.js b/ShadowEditor.Core/src/utils/GeometryUtils.js
new file mode 100644
index 00000000..f48d0be7
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/GeometryUtils.js
@@ -0,0 +1,61 @@
+/**
+ * 判断是否是three.js内置类
+ * @author tengge / https://github.com/tengge1
+ * @param {*} geometry 几何体
+ */
+function isBuildInGeometry(geometry) {
+ if (geometry instanceof THREE.BoxBufferGeometry ||
+ geometry instanceof THREE.BoxGeometry ||
+ geometry instanceof THREE.CircleBufferGeometry ||
+ geometry instanceof THREE.CircleGeometry ||
+ geometry instanceof THREE.ConeBufferGeometry ||
+ geometry instanceof THREE.ConeGeometry ||
+ geometry instanceof THREE.CylinderBufferGeometry ||
+ geometry instanceof THREE.CylinderGeometry ||
+ geometry instanceof THREE.DodecahedronBufferGeometry ||
+ geometry instanceof THREE.DodecahedronGeometry ||
+ geometry instanceof THREE.ExtrudeBufferGeometry ||
+ geometry instanceof THREE.ExtrudeGeometry ||
+ geometry instanceof THREE.IcosahedronBufferGeometry ||
+ geometry instanceof THREE.IcosahedronGeometry ||
+ geometry instanceof THREE.LatheBufferGeometry ||
+ geometry instanceof THREE.LatheGeometry ||
+ geometry instanceof THREE.OctahedronBufferGeometry ||
+ geometry instanceof THREE.OctahedronGeometry ||
+ geometry instanceof THREE.ParametricBufferGeometry ||
+ geometry instanceof THREE.ParametricGeometry ||
+ geometry instanceof THREE.PlaneBufferGeometry ||
+ geometry instanceof THREE.PlaneGeometry ||
+ geometry instanceof THREE.PolyhedronBufferGeometry ||
+ geometry instanceof THREE.PolyhedronGeometry ||
+ geometry instanceof THREE.RingBufferGeometry ||
+ geometry instanceof THREE.RingGeometry ||
+ geometry instanceof THREE.ShapeBufferGeometry ||
+ geometry instanceof THREE.ShapeGeometry ||
+ geometry instanceof THREE.SphereBufferGeometry ||
+ geometry instanceof THREE.SphereGeometry ||
+ geometry instanceof THREE.TetrahedronBufferGeometry ||
+ geometry instanceof THREE.TetrahedronGeometry ||
+ geometry instanceof THREE.TextBufferGeometry ||
+ geometry instanceof THREE.TextGeometry ||
+ geometry instanceof THREE.TorusBufferGeometry ||
+ geometry instanceof THREE.TorusGeometry ||
+ geometry instanceof THREE.TorusKnotBufferGeometry ||
+ geometry instanceof THREE.TorusKnotGeometry ||
+ geometry instanceof THREE.TubeBufferGeometry ||
+ geometry instanceof THREE.TubeGeometry
+ ) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 几何体工具类
+ */
+const GeometryUtils = {
+ isBuildInGeometry: isBuildInGeometry
+};
+
+export default GeometryUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/ImageUtils.js b/ShadowEditor.Core/src/utils/ImageUtils.js
new file mode 100644
index 00000000..ef866620
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/ImageUtils.js
@@ -0,0 +1,22 @@
+/**
+ * 产生一个单像素画布
+ * @param {*} color 默认颜色
+ */
+function onePixelCanvas(color = '#000000') {
+ var canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 1, 1);
+ return canvas;
+}
+
+/**
+ * 图片工具类
+ */
+const ImageUtils = {
+ onePixelCanvas: onePixelCanvas
+};
+
+export default ImageUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/JsUtils.js b/ShadowEditor.Core/src/utils/JsUtils.js
new file mode 100644
index 00000000..c570ed54
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/JsUtils.js
@@ -0,0 +1,30 @@
+/**
+ * 异步加载js文件
+ * @author tengge / https://github.com/tengge1
+ * @param {*} url js文件url
+ * @param {*} callback 回调函数
+ */
+function loadJs(url, callback) {
+ var head = document.getElementsByTagName('head')[0];
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ head.appendChild(script);
+ if (typeof (callback) === 'function') {
+ script.onload = script.onreadystatechange = function () {
+ if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
+ callback();
+ script.onload = script.onreadystatechange = null;
+ }
+ };
+ }
+};
+
+/**
+ * js工具类
+ */
+const JsUtils = {
+ load: loadJs
+};
+
+export default JsUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/MIMETypeUtils.js b/ShadowEditor.Core/src/utils/MIMETypeUtils.js
new file mode 100644
index 00000000..6d8ed90c
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/MIMETypeUtils.js
@@ -0,0 +1,51 @@
+/**
+ * 获取MIME-Type后缀名
+ * @param {*} mimeType MIME-Type
+ */
+function getExtension(mimeType) {
+ switch (mimeType) {
+ case 'image/jpeg':
+ return 'jpg';
+ case 'image/png':
+ return 'png';
+ case 'image/gif':
+ return 'gif';
+ case 'image/bmp':
+ return 'bmp';
+ default:
+ console.error(`MIMETypeUtils: 未知MIME-Type类型:${mimeType}`);
+ return 'unknown';
+ }
+}
+
+/**
+ * 获取MIME-Type类型
+ * @param {*} extension 文件名后缀
+ */
+function getMIMEType(extension) {
+ extension = extension.trimLeft('.');
+ switch (extension.toLowerCase()) {
+ case 'jpg':
+ case 'jpeg':
+ return 'image/jpeg';
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ case 'bmp':
+ return 'image/bmp';
+ default:
+ console.warn(`MIMETypeUtils: 未知后缀类型: ${extension}`);
+ return 'application/octet-stream';
+ }
+}
+
+/**
+ * MIME-Type工具类
+ */
+const MIMETypeUtils = {
+ getExtension: getExtension,
+ getMIMEType: getMIMEType,
+};
+
+export default MIMETypeUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/MathUtils.js b/ShadowEditor.Core/src/utils/MathUtils.js
new file mode 100644
index 00000000..c24c7d21
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/MathUtils.js
@@ -0,0 +1,24 @@
+/**
+ * 精度
+ * @author mrdoob / http://mrdoob.com/
+ */
+var NUMBER_PRECISION = 6;
+
+/**
+ * 打包数字
+ * @param {*} key
+ * @param {*} value
+ */
+function parseNumber(key, value) {
+ return typeof value === 'number' ? parseFloat(value.toFixed(NUMBER_PRECISION)) : value;
+}
+
+/**
+ * 数学工具
+ */
+const MathUtils = {
+ NUMBER_PRECISION: NUMBER_PRECISION,
+ parseNumber: parseNumber
+};
+
+export default MathUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/Socket.js b/ShadowEditor.Core/src/utils/Socket.js
new file mode 100644
index 00000000..92b59823
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/Socket.js
@@ -0,0 +1,43 @@
+import { dispatch } from '../third_party';
+
+/**
+ * Socket工具类
+ * @author tengge / https://github.com/tengge1
+ * @param {*} url Socket服务器地址
+ */
+function Socket(url) {
+ this.url = url;
+ this.reconnectTime = 5000; // 重新连接时间
+
+ this.socket = new WebSocket(this.url);
+
+ this.dispatch = dispatch('open', 'message', 'error', 'close');
+
+ var _this = this;
+ this.socket.onopen = function (evt) {
+ _this.dispatch.call.apply(_this.dispatch, arguments);
+ };
+
+ this.socket.onmessage = function (evt) {
+ _this.dispatch.call.apply(_this.dispatch, arguments);
+ };
+
+ this.socket.onerror = function (evt) {
+ _this.dispatch.call.apply(_this.dispatch, arguments);
+ };
+
+ this.socket.onclose = function (evt) {
+ _this.dispatch.call.apply(_this.dispatch, arguments);
+ if (this.reconnectTime != null) {
+ setTimeout(function () {
+ _this.socket = new WebSocket(this.url);
+ }, this.reconnectTime);
+ }
+ };
+}
+
+Socket.prototype.on = function (eventName, callback) {
+ this.dispatch.on(eventName, callback);
+};
+
+export default Socket;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/StringUtils.js b/ShadowEditor.Core/src/utils/StringUtils.js
new file mode 100644
index 00000000..0dadb807
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/StringUtils.js
@@ -0,0 +1,41 @@
+var link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link); // Firefox workaround, see #6594
+
+/**
+ * 将数字凑成2的指数次幂
+ * @author mrdoob / http://mrdoob.com/
+ * @author tengge / https://github.com/tengge1
+ * @param {*} num 数字
+ */
+function makePowOfTwo(num) {
+ var result = 1;
+ while (result < num) {
+ result = result * 2;
+ }
+ return result;
+}
+
+function save(blob, filename) {
+ link.href = URL.createObjectURL(blob);
+ link.download = filename || 'data.json';
+ link.click();
+ // URL.revokeObjectURL( url ); breaks Firefox...
+}
+
+/**
+ * 下载字符串文件
+ * @param {*} text
+ * @param {*} filename
+ */
+function saveString(text, filename) {
+ save(new Blob([text], { type: 'text/plain' }), filename);
+}
+
+const StringUtils = {
+ makePowOfTwo: makePowOfTwo,
+ save: save,
+ saveString: saveString
+};
+
+export default StringUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/utils/UploadUtils.js b/ShadowEditor.Core/src/utils/UploadUtils.js
new file mode 100644
index 00000000..9f537066
--- /dev/null
+++ b/ShadowEditor.Core/src/utils/UploadUtils.js
@@ -0,0 +1,40 @@
+/**
+ * 文件上传器
+ * @author tengge / https://github.com/tengge1
+ */
+function Uploader() {
+
+}
+
+/**
+ * 上传文件
+ * @param {*} input_id 文件input的id
+ * @param {*} url 后台接收上传文件url
+ * @param {*} onload 上传完成回调函数
+ * @param {*} onerror 上传出错回调函数
+ * @param {*} onprogress 上传过程回调函数
+ */
+Uploader.prototype.upload = function (input_id, url, onload, onerror, onprogress) {
+ var fileObj = document.getElementById(input_id).files[0];
+
+ var form = new FormData();
+ form.append("file", fileObj);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("post", url, true);
+ xhr.onload = onload;
+ xhr.onerror = onerror;
+ xhr.upload.onprogress = onprogress;
+ xhr.send(form);
+};
+
+const uploader = new Uploader();
+
+/**
+ * 上传工具类
+ */
+const UploadUtils = {
+ upload: uploader.upload
+};
+
+export default UploadUtils;
\ No newline at end of file
diff --git a/ShadowEditor.Core/src/worker/Worker.js b/ShadowEditor.Core/src/worker/Worker.js
new file mode 100644
index 00000000..e69de29b
diff --git a/package.json b/package.json
index 4a836663..13c9d218 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"build-pm": "rollup -c ShadowEditor.PackageManager/rollup.config.js",
"build-ui": "rollup -c ShadowEditor.UI/rollup.config.js",
"build-code": "rollup -c ShadowEditor.ScriptEditor/rollup.config.js",
+ "build-core": "rollup -c ShadowEditor.Core/rollup.config.js",
"build-player": "rollup -c ShadowEditor.Player/rollup.config.js",
"build-docs": "gitbook build docs-dev",
"clear": "npm prune"