├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── after_effects_export
├── adobe_tools.nim
├── after_effects.nim
├── convert_to_null.nim
├── gradient_property.nim
├── nakefile.nim
├── rod_export.nim
└── samples
│ ├── main.nim
│ └── res
│ └── compositions
│ ├── TEST1.json
│ ├── TEST2.json
│ ├── TEST3.json
│ └── TEST4.json
├── deployGHPages.sh
├── doc
└── index.rst
├── editor
├── main.nim
├── nakefile.nim
├── res
│ ├── BoyzRGross.ttf
│ ├── Jsondata
│ │ ├── Spot.json
│ │ ├── Spot.png
│ │ ├── explosion.png
│ │ ├── particle.png
│ │ └── ps.json
│ └── collada
│ │ ├── T-shape.dae
│ │ ├── arrow.dae
│ │ ├── balloon_animation_test.dae
│ │ ├── balloons_test.dae
│ │ ├── baloon_star_color.png
│ │ ├── baloon_star_normals.png
│ │ ├── baloon_star_reflective.jpg
│ │ └── motion.dae
├── rodedit.html
├── rodedit.nim
└── rodedit_main.nim
├── nakefile.nim
├── rod.nimble
├── rod.nims
├── rod
├── animated_image.nim
├── animation
│ ├── animation_sampler.nim
│ └── property_animation.nim
├── asset_bundle.nim
├── component.nim
├── component
│ ├── ae_composition.nim
│ ├── all_components.nim
│ ├── animation
│ │ └── skeleton.nim
│ ├── animation_runner.nim
│ ├── blink.nim
│ ├── blur_component.nim
│ ├── camera.nim
│ ├── channel_levels.nim
│ ├── clipping_rect_component.nim
│ ├── color_balance_hls.nim
│ ├── color_fill.nim
│ ├── comp_ref.nim
│ ├── fxaa_post.nim
│ ├── gradient_fill.nim
│ ├── light.nim
│ ├── mask.nim
│ ├── material.nim
│ ├── material_shaders.nim
│ ├── mesh_component.nim
│ ├── nine_part_sprite.nim
│ ├── overlay.nim
│ ├── particle_emitter.nim
│ ├── particle_helpers.nim
│ ├── particle_system.nim
│ ├── primitives
│ │ ├── cone.nim
│ │ ├── cube.nim
│ │ ├── icosphere.nim
│ │ └── sphere.nim
│ ├── rti.nim
│ ├── solid.nim
│ ├── sprite.nim
│ ├── text_component.nim
│ ├── tint.nim
│ ├── tracer.nim
│ ├── trail.nim
│ ├── ui_component.nim
│ ├── vector_shape.nim
│ └── visual_modifier.nim
├── dae_animation.nim
├── edit_view.nim
├── editor
│ ├── animation
│ │ ├── animation_chart_view.nim
│ │ ├── animation_editor_types.nim
│ │ ├── animation_key_inspector.nim
│ │ ├── dopesheet_view.nim
│ │ └── editor_animation_view.nim
│ ├── editor_assets_view.nim
│ ├── editor_console.nim
│ ├── editor_default_tabs.nim
│ ├── editor_error_handling.nim
│ ├── editor_inspector_view.nim
│ ├── editor_project_settings.nim
│ ├── editor_tab_registry.nim
│ ├── editor_tree_view.nim
│ ├── editor_types.nim
│ ├── editor_workspace_view.nim
│ ├── file_system
│ │ ├── editor_asset_container_view.nim
│ │ ├── editor_asset_icon_view.nim
│ │ └── filesystem_view.nim
│ └── scene
│ │ ├── components
│ │ ├── editor_component.nim
│ │ ├── grid.nim
│ │ └── viewport_rect.nim
│ │ ├── editor_camera_controller.nim
│ │ ├── editor_scene_settings.nim
│ │ ├── editor_scene_view.nim
│ │ ├── gizmo.nim
│ │ ├── gizmo_move.nim
│ │ └── node_selector.nim
├── material
│ └── shader.nim
├── mesh.nim
├── message_queue.nim
├── node.nim
├── postprocess_context.nim
├── property_editors
│ ├── propedit_registry.nim
│ ├── rodedit_editors.nim
│ └── standard_editors.nim
├── quaternion.nim
├── ray.nim
├── rod_types.nim
├── scene_composition.nim
├── system
│ └── messaging.nim
├── systems.nim
├── tools
│ ├── debug_draw.nim
│ ├── rodasset
│ │ ├── asset_cache.nim
│ │ ├── binformat.nim
│ │ ├── imgtool.nim
│ │ ├── migrator.nim
│ │ ├── rodasset.nim
│ │ ├── rodasset.nims
│ │ ├── rodasset_main.nim
│ │ ├── settings.nim
│ │ └── tree_traversal.nim
│ ├── serializer.nim
│ └── tool_wrapper.nim
├── utils
│ ├── attributed_text.nim
│ ├── bin_deserializer.nim
│ ├── bin_serializer.nim
│ ├── image_serialization.nim
│ ├── json_deserializer.nim
│ ├── json_serializer.nim
│ ├── property_desc.nim
│ ├── serialization_codegen.nim
│ ├── serialization_hash_calculator.nim
│ ├── serialization_helpers.nim
│ └── text_helpers.nim
├── vertex_data_info.nim
└── viewport.nim
└── template
├── .gitignore
├── main.nim
├── nakefile.nim
├── res
└── example_bundle
│ ├── composition2.jcomp
│ ├── config.rab
│ └── logo-crown.png
├── src
├── core
│ ├── asset_loader.nim
│ └── game_scene.nim
└── game
│ └── example_scene.nim
└── template.nimble
/.gitignore:
--------------------------------------------------------------------------------
1 | nimcache/
2 | editor/main
3 | editor/nakefile
4 | build
5 | nvcompatlog.txt
6 | rod/tools/rodimgtool
7 | template/nimcache
8 | template/nakefile
9 | *.exe
10 |
11 | nakefile
12 | baner_01.png
13 | banner_2+.dae
14 | banner_2.dae
15 | rod/tools/rodasset/rodasset
16 | editor/res/collada/bug3.dae
17 | editor/res/collada/bug4.dae
18 | editor/res/collada/bug_sceene_1.mb
19 | editor/res/Jsondata/box.png
20 | editor/res/Jsondata/circle.png
21 | editor/res/Jsondata/phys.json
22 | editor/res/Jsondata/phys_ball.json
23 | editor/res/Jsondata/phys_box.json
24 | editor/res/Jsondata/test.json
25 | editor/res/Jsondata/wall.png
26 | editor/rodedit
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | services:
3 | - docker
4 | before_install:
5 | - docker pull yglukhov/nim-ui-base
6 | script:
7 | - docker run -v "$(pwd):/project" -w /project yglukhov/nim-ui-base run "nimble install -y && nake tests"
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Yuriy Glukhov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rod [](https://travis-ci.org/yglukhov/rod)
2 |
3 | Rod is a cross-platform 2d and 3d game engine for Nim.
4 |
5 |
8 |
9 | Installation:
10 | ```
11 | $ git clone https://github.com/yglukhov/rod
12 | $ cd rod
13 | $ nimble install -y
14 | ```
15 |
16 | Update all dependencies:
17 | ```
18 | $ cd rod
19 | $ nimble install -y -d
20 | ```
21 |
22 | Supported platforms:
23 | * iOS
24 | * Android
25 | * Windows
26 | * OSX
27 | * Linux
28 | * HTML5 (Emscripten)
29 |
30 | Build Requirements:
31 | * Nim latest devel version
32 | * Nimble package manager
33 |
--------------------------------------------------------------------------------
/after_effects_export/adobe_tools.nim:
--------------------------------------------------------------------------------
1 | # Definitions according to Adobe JAVASCRIPT TOOLS GUIDE
2 |
3 | type
4 | File* = ref FileObj
5 | FileObj {.importc.} = object of RootObj
6 | name*: cstring
7 | path*: cstring
8 | encoding*: cstring
9 | lineFeed*: cstring # lfWindows, lfUnix or lfMacintosh
10 | parent*: Folder
11 |
12 | Folder* = ref FolderObj
13 | FolderObj {.importc.} = object of RootObj
14 | name*: cstring
15 | exists*: bool
16 |
17 | proc initMathTruncPolyfill() =
18 | # After effects doesn't have Math.truc func...
19 | {.emit: """
20 | if (!Math.trunc) {
21 | Math.trunc = function(v) {
22 | v = +v;
23 | return (v - v % 1) || (!isFinite(v) || v === 0 ? v : v < 0 ? -0 : 0);
24 | };
25 | }
26 | """.}
27 |
28 | initMathTruncPolyfill()
29 |
30 | const lfWindows*: cstring = "Windows"
31 | const lfUnix*: cstring = "Unix"
32 | const lfMacintosh*: cstring = "Macintosh"
33 |
34 | proc newFile*(path: cstring): File {.importc: "new File".}
35 | proc open*(f: File, mode: cstring) {.importcpp.}
36 | template openForWriting*(f: File) = f.open("w")
37 | proc write*(f: File, content: cstring) {.importcpp.}
38 | proc close*(f: File) {.importcpp.}
39 | proc copy*(src, target: File): bool {.importcpp.}
40 | proc copy*(src: File, targetPath: cstring): bool {.importcpp.}
41 |
42 | proc newFolder*(path: cstring): Folder {.importc: "new Folder".}
43 | proc getFiles*(f: Folder): seq[File] {.importcpp.}
44 | proc create*(f: Folder): bool {.importcpp.}
45 | proc execute*(f: Folder): bool {.importcpp.}
46 | proc remove*(f: Folder): bool {.importcpp.}
47 |
--------------------------------------------------------------------------------
/after_effects_export/convert_to_null.nim:
--------------------------------------------------------------------------------
1 | import tables, dom, math
2 | import after_effects
3 | import times
4 | import json
5 | import algorithm
6 | import strutils
7 | import nimx/matrixes, nimx/pathutils
8 | import rod/quaternion
9 |
10 | type File = after_effects.File
11 |
12 | var logTextField: EditText
13 |
14 | proc logi(args: varargs[string, `$`]) =
15 | var text = $logTextField.text
16 | for i in args:
17 | text &= i
18 | text &= "\n"
19 | logTextField.text = text
20 |
21 | proc exportSelectedCompositions(exportFolderPath: cstring) {.exportc.} =
22 | let comp = app.project.activeItem(Composition)
23 | let layers = comp.selectedLayers
24 | for layer in layers:
25 | if not layer.nullLayer:
26 | logi "Converting layer: ", layer.name
27 | let newLayer = comp.layers.addNull()
28 | newLayer.name = layer.name
29 | newLayer.property("Position", Vector3).setValue(layer.property("Position", Vector3).value)
30 | layer.remove()
31 |
32 | logi("Done. ", epochTime())
33 |
34 | {.emit: """
35 |
36 | function buildUI(contextObj) {
37 | var mainWindow = null;
38 | if (contextObj instanceof Panel) {
39 | mainWindow = contextObj;
40 | } else {
41 | mainWindow = new Window("palette", "Animations", undefined, {
42 | resizeable: true
43 | });
44 | mainWindow.size = [640, 300];
45 | }
46 | //mainWindow.alignment = ['fill', 'fill'];
47 |
48 | var topGroup = mainWindow.add("group{orientation:'row'}");
49 | topGroup.alignment = ["fill", "top"];
50 |
51 | var setPathButton = topGroup.add("button", undefined, "Browse");
52 | setPathButton.alignment = ["left", "center"];
53 |
54 | var filePath = topGroup.add("statictext");
55 | filePath.alignment = ["fill", "fill"];
56 |
57 | var exportButton = topGroup.add("button", undefined,
58 | "Export selected compositions");
59 | exportButton.alignment = ["right", "center"];
60 | exportButton.enabled = false;
61 |
62 | if (app.settings.haveSetting("rodExport", "outputPath")) {
63 | exportButton.enabled = true;
64 | filePath.text = app.settings.getSetting("rodExport", "outputPath");
65 | } else {
66 | filePath.text = "Output: (not specified)";
67 | }
68 |
69 | var resultText = mainWindow.add(
70 | "edittext{alignment:['fill','fill'], properties: { multiline:true } }");
71 | `logTextField`[0] = resultText;
72 |
73 | setPathButton.onClick = function(e) {
74 | var outputFile = Folder.selectDialog("Choose an output folder");
75 | if (outputFile) {
76 | exportButton.enabled = true;
77 | filePath.text = outputFile.absoluteURI;
78 | app.settings.saveSetting("rodExport", "outputPath", outputFile.absoluteURI);
79 | } else {
80 | exportButton.enabled = false;
81 | }
82 | };
83 |
84 | exportButton.onClick = function(e) {
85 | `logTextField`[0].text = "";
86 | exportSelectedCompositions(filePath.text);
87 | };
88 |
89 | mainWindow.addEventListener("resize", function(e) {
90 | this.layout.resize();
91 | });
92 |
93 | mainWindow.addEventListener("close", function(e) {
94 | app.cancelTask(taskId);
95 | stopServer();
96 | });
97 |
98 | mainWindow.onResizing = mainWindow.onResize = function() {
99 | this.layout.resize();
100 | };
101 |
102 | if (mainWindow instanceof Window) {
103 | // mainWindow.onShow = function() {
104 | // readMetaData();
105 | // }
106 | mainWindow.show();
107 | } else {
108 | mainWindow.layout.layout(true);
109 | // readMetaData();
110 | }
111 | }
112 |
113 | buildUI(this);
114 |
115 | """.}
116 |
--------------------------------------------------------------------------------
/after_effects_export/gradient_property.nim:
--------------------------------------------------------------------------------
1 | import after_effects
2 | import json
3 |
4 | proc getGradientColors*(gradientOverlay: PropertyGroup, comp: Composition): array[2, array[4, float]] =
5 | const undoGroupId = "__rod_export_script_undo_group__"
6 | app.beginUndoGroup(undoGroupId)
7 |
8 | let color = [1.0, 1.0, 1.0]
9 | let composSize = comp
10 | let tempLayer = comp.layers.addSolid(color, "temp", comp.width, comp.height, 1.0)
11 |
12 | tempLayer.selected = true
13 | app.executeCommand(app.findMenuCommandId("Gradient Overlay"))
14 | tempLayer.selected = false
15 | gradientOverlay.property("Colors").selected = true
16 | app.executeCommand(app.findMenuCommandId("Copy"))
17 | gradientOverlay.property("Colors").selected = false
18 | tempLayer.propertyGroup("Layer Styles").propertyGroup("Gradient Overlay").selected = true
19 | app.executeCommand(app.findMenuCommandId("Paste"))
20 | tempLayer.propertyGroup("Layer Styles").propertyGroup("Gradient Overlay").selected = false
21 | tempLayer.property("Position", array[3, float32]).setValue([0'f32, 0, 0])
22 | tempLayer.property("anchorPoint", array[3, float32]).setValue([0'f32, 0, 0])
23 |
24 | let newComp = comp.layers.precompose(@[1], "tempComp", true)
25 | let tempLayerText = comp.layers.addText()
26 | let tempText = tempLayerText.propertyGroup("Text").property("Source Text", TextDocument)
27 |
28 | tempText.expression = "thisComp.layer(\"tempComp\").sampleImage([0, 0], [0.5, 0.5], true).toSource()"
29 | for i in 0 ..< 1000:
30 | if $tempText.valueAtTime(0, false).text != "[0, 0, 0, 0]": break
31 |
32 | # end color
33 | let c0 = parseJson($tempText.value.text)
34 | result[1] = [c0[0].getFloat(), c0[1].getFloat(), c0[2].getFloat(), c0[3].getFloat()]
35 |
36 | tempText.expression = "thisComp.layer(\"tempComp\").sampleImage([0, " & $(comp.height - 1) & "], [0.5, 0.5], true).toSource()"
37 | for i in 0 ..< 1000:
38 | if $tempText.valueAtTime(0, false).text != "[0, 0, 0, 0]": break
39 |
40 | # start color
41 | let c1 = parseJson($tempText.value.text)
42 | result[0] = [c1[0].getFloat(), c1[1].getFloat(), c1[2].getFloat(), c1[3].getFloat()]
43 |
44 | app.endUndoGroup()
45 | app.undo(undoGroupId)
--------------------------------------------------------------------------------
/after_effects_export/nakefile.nim:
--------------------------------------------------------------------------------
1 | import nake
2 | import os
3 |
4 | when defined(windows):
5 | const searchPaths = [
6 | "C:/Program Files/Adobe/Adobe After Effects CC 2015.3/Support Files/Scripts",
7 | "C:/Program Files/Adobe/Adobe After Effects CC 2017/Support Files/Scripts",
8 | "C:/Program Files/Adobe/Adobe After Effects CC 2018/Support Files/Scripts"
9 | ]
10 | else:
11 | const searchPaths = [
12 | "/Applications/Adobe After Effects CC 2015/Scripts",
13 | "/Applications/Adobe After Effects CC 2015.3/Scripts",
14 | "/Applications/Adobe After Effects CC 2017/Scripts",
15 | "/Applications/Adobe After Effects CC 2018/Scripts"
16 | ]
17 |
18 | task defaultTask, "Build and install":
19 | direShell nimExe, "js", "--warning[LockLevel]:off", "rod_export.nim"
20 | direShell nimExe, "js", "--warning[LockLevel]:off", "convert_to_null.nim"
21 | var ok = false
22 | for sp in searchPaths:
23 | if dirExists(sp):
24 | createDir(sp / "ScriptUI Panels")
25 | copyFile("nimcache/rod_export.js", sp / "ScriptUI Panels/rod_export.jsx")
26 | copyFile("nimcache/convert_to_null.js", sp / "ScriptUI Panels/convert_to_null.jsx")
27 | ok = true
28 | if not ok:
29 | raise newException(Exception, "After Effect not installed or not found!")
--------------------------------------------------------------------------------
/after_effects_export/samples/main.nim:
--------------------------------------------------------------------------------
1 | import nimx/matrixes
2 | import rod/viewport
3 | import rod/edit_view
4 | import rod/component/camera
5 | import rod/node
6 |
7 | import rod/component/solid
8 |
9 | when defined js:
10 | import nimx.js_canvas_window
11 | type PlatformWindow = JSCanvasWindow
12 | else:
13 | import nimx.sdl_window
14 | type PlatformWindow = SdlWindow
15 |
16 | const isMobile = defined(ios) or defined(android)
17 |
18 | type SampleView = ref object of View
19 | viewport: Viewport
20 |
21 | method draw*(ev: SampleView, r: Rect) =
22 | ev.viewport.draw()
23 |
24 | proc startApplication() =
25 | var mainWindow : PlatformWindow
26 | mainWindow.new()
27 |
28 | when isMobile:
29 | mainWindow.initFullscreen()
30 | else:
31 | mainWindow.init(newRect(40, 40, 1200, 600))
32 |
33 | mainWindow.title = "Rod"
34 |
35 | let editView = SampleView.new(mainWindow.bounds)
36 | editView.autoresizingMask = { afFlexibleWidth, afFlexibleHeight }
37 | editView.viewport.new()
38 | editView.viewport.view = editView
39 | editView.viewport.rootNode = newNode()
40 | let cameraNode = editView.viewport.rootNode.newChild("camera")
41 | let camera = cameraNode.component(Camera)
42 | camera.projectionMode = cpOrtho
43 | cameraNode.positionZ = 1
44 |
45 | mainWindow.addSubview(editView)
46 | mainWindow.addAnimation(c.animationNamed("anim1"))
47 |
48 |
49 | runApplication:
50 | startApplication()
51 |
--------------------------------------------------------------------------------
/after_effects_export/samples/res/compositions/TEST1.json:
--------------------------------------------------------------------------------
1 | {
"name": "TEST1",
"children": [
{
"name": "Blue Solid 1",
"translation": [
0,
0,
0
],
"scale": [
0.29605263157895,
0.3,
1
],
"components": {
"Solid": {
"color": [
0,
0.15431976318359,
1
],
"size": [
512,
512
]
}
}
}
],
"animations": {
"anim1": {
"Blue Solid 1.translation": {
"duration": 0.73333333333333,
"values": [
[
0,
0,
0
],
[
72.9417802377778,
-8.36383182947478,
0
],
[
145.692164841849,
-18.5025008485039,
0
],
[
218.803330587628,
-25.4667145009578,
0
],
[
292.182724738109,
-24.656946944831,
0
],
[
360,
0,
0
],
[
392.357058474652,
67.0652239755088,
0
],
[
403.713694895556,
141.38692530722,
0
],
[
403.714291318908,
216.604453875111,
0
],
[
392.356973614675,
290.935106949027,
0
],
[
360,
358,
0
],
[
292.470941549469,
390.199895070584,
0
],
[
217.777091129965,
401.473994300279,
0
],
[
142.214104696735,
401.473392563907,
0
],
[
67.5294017906095,
390.199982028381,
0
],
[
0,
358,
0
],
[
-24.7854537917066,
290.645099378283,
0
],
[
-25.6122641825846,
217.644673436292,
0
],
[
-18.6100608109513,
144.916035565569,
0
],
[
-8.41329818435354,
72.5550852432787,
0
],
[
0,
0,
0
],
[
0,
0,
0
]
]
}
}
}
}
--------------------------------------------------------------------------------
/after_effects_export/samples/res/compositions/TEST2.json:
--------------------------------------------------------------------------------
1 | {
"name": "TEST2",
"children": [
{
"name": "Blue Solid 1",
"translation": [
66,
68,
0
],
"scale": [
0.3,
0.3,
1
],
"components": {
"Solid": {
"color": [
0,
0.15431976318359,
1
],
"size": [
512,
512
]
}
}
}
],
"animations": {
"anim1": {
"Blue Solid 1.scale": {
"duration": 0.73333333333333,
"values": [
[
0.3,
0.3,
1
],
[
0.34,
0.34,
1
],
[
0.38,
0.38,
1
],
[
0.42,
0.42,
1
],
[
0.46,
0.46,
1
],
[
0.5,
0.5,
1
],
[
0.46,
0.46,
1
],
[
0.42,
0.42,
1
],
[
0.38,
0.38,
1
],
[
0.34,
0.34,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
]
],
"numberOfLoops": -1
}
}
}
}
--------------------------------------------------------------------------------
/after_effects_export/samples/res/compositions/TEST3.json:
--------------------------------------------------------------------------------
1 | {
"name": "TEST3",
"children": [
{
"name": "Blue Solid 1$AUX",
"translation": [
200,
200,
0
],
"scale": [
0.3,
0.3,
1
],
"children": [
{
"name": "Blue Solid 1",
"translation": [
-260,
-260,
0
],
"components": {
"Solid": {
"color": [
0,
0.15431976318359,
1
],
"size": [
512,
512
]
}
}
}
]
},
{
"name": "Yellow Solid",
"translation": [
200,
200,
0
],
"scale": [
0.109375,
0.1015625,
1
],
"components": {
"Solid": {
"color": [
0.95946687459946,
1,
0
],
"size": [
512,
512
]
}
}
}
],
"animations": {
"anim1": {
"Blue Solid 1$AUX.scale": {
"duration": 0.73333333333333,
"values": [
[
0.3,
0.3,
1
],
[
0.34,
0.34,
1
],
[
0.38,
0.38,
1
],
[
0.42,
0.42,
1
],
[
0.46,
0.46,
1
],
[
0.5,
0.5,
1
],
[
0.46,
0.46,
1
],
[
0.42,
0.42,
1
],
[
0.38,
0.38,
1
],
[
0.34,
0.34,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
]
],
"numberOfLoops": -1
}
}
}
}
--------------------------------------------------------------------------------
/after_effects_export/samples/res/compositions/TEST4.json:
--------------------------------------------------------------------------------
1 | {
"name": "TEST4",
"children": [
{
"name": "Blue Solid 1$AUX",
"translation": [
200,
200,
0
],
"scale": [
0.3,
0.3,
1
],
"children": [
{
"name": "Blue Solid 1",
"translation": [
-260,
-260,
0
],
"components": {
"Solid": {
"color": [
0,
0.15431976318359,
1
],
"size": [
512,
512
]
}
}
}
]
},
{
"name": "Yellow Solid",
"translation": [
200,
200,
0
],
"scale": [
0.109375,
0.1015625,
1
],
"components": {
"Solid": {
"color": [
0.95946687459946,
1,
0
],
"size": [
512,
512
]
}
}
}
],
"animations": {
"anim1": {
"Blue Solid 1$AUX.scale": {
"duration": 0.73333333333333,
"values": [
[
0.3,
0.3,
1
],
[
0.34,
0.34,
1
],
[
0.38,
0.38,
1
],
[
0.42,
0.42,
1
],
[
0.46,
0.46,
1
],
[
0.5,
0.5,
1
],
[
0.46,
0.46,
1
],
[
0.42,
0.42,
1
],
[
0.38,
0.38,
1
],
[
0.34,
0.34,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
],
[
0.3,
0.3,
1
]
],
"numberOfLoops": -1
},
"Blue Solid 1$AUX.rotation": {
"duration": 0.73333333333333,
"values": [
[
0,
0,
0,
1
],
[
0,
0,
0.07845909572784,
0.99691733373313
],
[
0,
0,
0.15643446504023,
0.98768834059514
],
[
0,
0,
0.23344536385591,
0.97236992039768
],
[
0,
0,
0.30901699437495,
0.95105651629515
],
[
0,
0,
0.38268343236509,
0.92387953251129
],
[
0,
0,
0.45399049973955,
0.89100652418837
],
[
0,
0,
0.52249856471595,
0.85264016435409
],
[
0,
0,
0.58778525229247,
0.80901699437495
],
[
0,
0,
0.64944804833018,
0.76040596560003
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
],
[
0,
0,
0.70710678118655,
0.70710678118655
]
],
"numberOfLoops": -1
}
}
}
}
--------------------------------------------------------------------------------
/deployGHPages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This script requires GH_KEY environment variable to be set to Github access
4 | # token. The token may be generated at https://github.com/settings/tokens and
5 | # has to have permissions to commit (e.g. public_repo)
6 |
7 | # The variable itself then has to be set in travis-ci project settings.
8 |
9 | if [ "$GH_KEY" \!= "" ]
10 | then
11 | export GIT_DIR=/tmp/gh-pages.tmp
12 | rm -rf "$GIT_DIR"
13 | mkdir -p "$GIT_DIR"
14 |
15 | cd "$1"
16 | export GIT_WORK_TREE=$(pwd)
17 | git init
18 |
19 | git config user.name "Travis CI"
20 | git config user.email "autodocgen@example.com"
21 |
22 | git add .
23 |
24 | git commit -m "Deploy to GitHub Pages"
25 |
26 | git push --force --quiet "https://$GH_KEY@github.com/$TRAVIS_REPO_SLUG" master:gh-pages > /dev/null 2>&1
27 | fi
28 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | ====
2 | Home
3 | ====
4 |
5 | Welcome to rod
6 | --------------
7 |
8 | **rod** is a game engine written in `Nim`_.
9 |
--------------------------------------------------------------------------------
/editor/main.nim:
--------------------------------------------------------------------------------
1 |
2 | include rodedit_main
3 |
--------------------------------------------------------------------------------
/editor/nakefile.nim:
--------------------------------------------------------------------------------
1 |
2 | import nimx/naketools
3 |
4 | withDir "..":
5 | direShell "nimble install -y"
6 |
7 | beforeBuild = proc(b: Builder) =
8 | #b.disableClosureCompiler = true
9 | b.mainFile = "rodedit_main"
10 | b.additionalCompilerFlags.add "-g"
11 |
--------------------------------------------------------------------------------
/editor/res/BoyzRGross.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/BoyzRGross.ttf
--------------------------------------------------------------------------------
/editor/res/Jsondata/Spot.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Bottom",
3 | "translation": [
4 | 0,
5 | 5,
6 | 0
7 | ],
8 | "children": [
9 | {
10 | "name": "Spot.png",
11 | "translation": [
12 | 20,
13 | 0,
14 | 0
15 | ],
16 | "scale": [
17 | 0.05,
18 | 0.05,
19 | 1.0
20 | ],
21 | "components":
22 | {
23 | "Sprite":
24 | {
25 | "fileNames": [
26 | "Spot.png"
27 | ]
28 | }
29 | }
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/editor/res/Jsondata/Spot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/Jsondata/Spot.png
--------------------------------------------------------------------------------
/editor/res/Jsondata/explosion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/Jsondata/explosion.png
--------------------------------------------------------------------------------
/editor/res/Jsondata/particle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/Jsondata/particle.png
--------------------------------------------------------------------------------
/editor/res/Jsondata/ps.json:
--------------------------------------------------------------------------------
1 | {
2 | "layer": 0,
3 | "name": "New Node",
4 | "components": [
5 | {
6 | "is2D": true,
7 | "_c": "ConePSGenShape",
8 | "angle": -20.0,
9 | "radius": 2.0
10 | },
11 | {
12 | "airDensity": 0.0,
13 | "startVelocity": 8.0,
14 | "lifetime": 8.0,
15 | "modifierNode": "attr",
16 | "duration": 3.0,
17 | "birthRate": 100.0,
18 | "randVelocityTo": 0.0,
19 | "randScaleTo": 0.0,
20 | "_c": "ParticleSystem",
21 | "randRotVelocityTo": [
22 | 0.0,
23 | 0.0,
24 | 0.0
25 | ],
26 | "scaleSeq": [],
27 | "startScale": [
28 | 3.0,
29 | 3.0,
30 | 3.0
31 | ],
32 | "randScaleFrom": 0.0,
33 | "colorMode": 0,
34 | "isLooped": true,
35 | "startColor": [
36 | 1.0,
37 | 0.0,
38 | 0.0,
39 | 1.0
40 | ],
41 | "randVelocityFrom": 0.0,
42 | "scaleMode": 0,
43 | "dstScale": [
44 | 0.0,
45 | 0.0,
46 | 0.0
47 | ],
48 | "genShapeNode": "New Node",
49 | "is3dRotation": false,
50 | "dstColor": [
51 | 1.0,
52 | 0.0,
53 | 0.0,
54 | 1.0
55 | ],
56 | "isBlendAdd": false,
57 | "gravity": [
58 | 0.0,
59 | 0.0,
60 | 0.0
61 | ],
62 | "randRotVelocityFrom": [
63 | 0.0,
64 | 0.0,
65 | 0.0
66 | ],
67 | "colorSeq": [
68 | [
69 | 0.0,
70 | 1.0,
71 | 0.0,
72 | 0.0,
73 | 1.0
74 | ],
75 | [
76 | 0.5,
77 | 0.0,
78 | 1.0,
79 | 0.0,
80 | 1.0
81 | ],
82 | [
83 | 1.0,
84 | 0.0,
85 | 0.0,
86 | 1.0,
87 | 1.0
88 | ]
89 | ],
90 | "isPlayed": true
91 | }
92 | ],
93 | "alpha": 1.0,
94 | "translation": [
95 | 0.0,
96 | 0.0,
97 | 0.0
98 | ],
99 | "scale": [
100 | 1.0,
101 | 1.0,
102 | 1.0
103 | ],
104 | "children": [],
105 | "rotation": [
106 | -0.5,
107 | 0.0,
108 | 0.0,
109 | 0.8660253882408142
110 | ]
111 | }
--------------------------------------------------------------------------------
/editor/res/collada/baloon_star_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/collada/baloon_star_color.png
--------------------------------------------------------------------------------
/editor/res/collada/baloon_star_normals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/collada/baloon_star_normals.png
--------------------------------------------------------------------------------
/editor/res/collada/baloon_star_reflective.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/editor/res/collada/baloon_star_reflective.jpg
--------------------------------------------------------------------------------
/editor/rodedit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/editor/rodedit.nim:
--------------------------------------------------------------------------------
1 | # This is a wrapper to rodedit_main. It compiles rodedit_main with rod
2 | # plugins of the project that calls the wrapper.
3 | import ../rod/tools/tool_wrapper
4 | runEditorWrapper("rodedit", "editor/rodedit_main.nim")
5 |
--------------------------------------------------------------------------------
/editor/rodedit_main.nim:
--------------------------------------------------------------------------------
1 | import tables, logging, strutils
2 |
3 | import nimx / [ matrixes, window, autotest ]
4 | import rod / [ edit_view ]
5 | import rod/editor/editor_error_handling
6 | import rod/component/all_components
7 |
8 | import rod/editor/editor_project_settings
9 |
10 | const rodPluginFile {.strdefine.} = ""
11 | when rodPluginFile.len != 0:
12 | import macros
13 | macro doImport(): untyped =
14 | newTree(nnkImportStmt, newLit(rodPluginFile))
15 | doImport()
16 |
17 | when defined(rodedit):
18 | import os
19 |
20 | const isMobile = defined(ios) or defined(android)
21 |
22 | proc runAutoTestsIfNeeded() =
23 | uiTest generalUITest:
24 | discard
25 | quitApplication()
26 |
27 | registerTest(generalUITest)
28 | when defined(runAutoTests):
29 | startRegisteredTests()
30 |
31 | proc switchToEditView(w: Window, proj: EditorProject)=
32 | discard w.startEditorForProject(proj)
33 |
34 | proc startApplication() =
35 | when isMobile or defined(js):
36 | var mainWindow = newFullscreenWindow()
37 | else:
38 | var mainWindow = newWindow(newRect(140, 40, 1600, 1000))
39 | when loadingAndSavingAvailable:
40 | when defined(rodedit):
41 | var proj: EditorProject
42 | proj.name = getAppDir().lastPathPart
43 | proj.path = getAppDir()
44 | mainWindow.title = "Project " & proj.name
45 | mainWindow.switchToEditView(proj)
46 | else:
47 | var proj: EditorProject
48 | mainWindow.title = "Rod"
49 | mainWindow.switchToEditView(proj)
50 |
51 | runAutoTestsIfNeeded()
52 |
53 | when defined(rodedit):
54 | onUnhandledException = proc(msg: string) =
55 | var msg = msg.indent(8)
56 | error "Exception caught:\n ", msg
57 |
58 | runApplication:
59 | startApplication()
60 |
--------------------------------------------------------------------------------
/nakefile.nim:
--------------------------------------------------------------------------------
1 | import nimx/naketools
2 | import osproc
3 |
4 | beforeBuild = proc(b: Builder) =
5 | #b.disableClosureCompiler = true
6 | b.mainFile = "editor/rodedit_main"
7 | b.originalResourcePath = "editor/res"
8 | b.additionalNimFlags.add("-d:rodplugin")
9 | b.additionalNimFlags.add("-d:rodedit")
10 | b.additionalNimFlags.add("--putenv:PREFS_FILE_NAME=rodedit.json")
11 |
12 | proc filterExceptions(name: string): bool =
13 | let exc = @["main", "nakefile", "rodedit", "rodasset"]
14 |
15 | for e in exc:
16 | let fileName = e & ".nim"
17 | if name.contains(e):
18 | return false
19 |
20 | result = true
21 |
22 | proc doMainModuleTests() =
23 | for f in walkDirRec "../rod/":
24 | if f.endsWith(".nim") and f.filterExceptions():
25 | let content = readFile(f)
26 |
27 | if content.contains("isMainModule"):
28 | direShell "nim c -r --threads:on " & f
29 |
30 | task "mtests", "Main module tests":
31 | doMainModuleTests()
32 |
33 | task "tests", "Build and run autotests":
34 | let b = newBuilder()
35 |
36 | if b.platform == "js":
37 | b.runAfterBuild = false
38 |
39 | b.additionalNimFlags.add "-d:runAutoTests"
40 | b.build()
41 |
42 | if b.platform == "js":
43 | b.runAutotestsInFirefox()
44 | doMainModuleTests()
45 |
46 | task "docs", "Build documentation":
47 | createDir "./build/doc"
48 | withDir "./build/doc":
49 | for t, f in walkDir "../../rod":
50 | if f.endsWith(".nim"):
51 | shell "nim doc2 -d:js " & f & " &>/dev/null"
52 |
53 | for t, f in walkDir "../../doc":
54 | if f.endsWith(".rst"):
55 | direShell "nim rst2html " & f & " &>/dev/null"
56 |
57 | copyDir "../js", "./livedemo"
58 |
--------------------------------------------------------------------------------
/rod.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 | version = "0.1.0"
3 | author = "Anonymous"
4 | description = "Graphics engine"
5 | license = "MIT"
6 |
7 | bin = @["rod/tools/rodasset/rodasset", "editor/rodedit"]
8 | installExt = @["nim"]
9 |
10 | # Dependencies
11 | requires "nimx"
12 | requires "https://github.com/SSPKrolik/nimasset#head"
13 | requires "variant"
14 | requires "os_files"
15 | requires "https://github.com/yglukhov/imgtools"
16 | requires "cligen"
17 | requires "untar#head"
18 | requires "tempfile"
19 | requires "https://github.com/yglukhov/threadpools"
20 | requires "https://github.com/yglukhov/preferences"
21 | requires "sha1"
22 | requires "https://github.com/yglukhov/clipboard"
23 |
--------------------------------------------------------------------------------
/rod.nims:
--------------------------------------------------------------------------------
1 |
2 | proc commonSetup() =
3 | --threads:on
4 | --noMain
5 | #switch("warning[LockLevel]", "off")
6 |
7 | task tests, "Autotests":
8 | commonSetup()
9 | --d:runAutoTests
10 | setCommand "c", "editor/rodedit"
11 |
12 | task editor, "Editor":
13 | commonSetup()
14 | setCommand "c", "editor/rodedit"
15 |
--------------------------------------------------------------------------------
/rod/animated_image.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, image, animation, portable_gl]
2 | import math
3 |
4 |
5 | type AnimatedImage* = ref object of Image
6 | images*: seq[Image]
7 | currentFrame*: int
8 | anim*: Animation
9 |
10 | proc image*(ai: AnimatedImage): Image =
11 | if ai.images.len > ai.currentFrame:
12 | result = ai.images[ai.currentFrame]
13 |
14 | proc `image=`*(ai: AnimatedImage, i: Image) =
15 | ai.images.setLen(1)
16 | ai.images[0] = i
17 | ai.currentFrame = 0
18 |
19 | proc newAnimatedImageWithImage*(i: Image): AnimatedImage =
20 | result.new()
21 | result.image = i
22 |
23 | proc newAnimatedImageWithImageSeq*(imgs: seq[Image]): AnimatedImage =
24 | result.new()
25 | result.images = imgs
26 |
27 | proc frameAnimation*(ai: AnimatedImage, desiredFramerate: int = 30): Animation =
28 | if ai.anim.isNil:
29 | if desiredFramerate == 0:
30 | raise newException(Exception, "Division by zero")
31 | let a = newAnimation()
32 | let fps = 1.0 / float(desiredFramerate)
33 | a.loopDuration = float(ai.images.len) * fps
34 | a.continueUntilEndOfLoopOnCancel = true
35 | a.onAnimate = proc(p: float) =
36 | ai.currentFrame = round(float(ai.images.len - 1) * p).int
37 | ai.anim = a
38 | result = ai.anim
39 |
40 | method isLoaded*(ai: AnimatedImage): bool =
41 | result = ai.images[ai.currentFrame].isLoaded()
42 |
43 | method getTextureQuad*(ai: AnimatedImage, gl: GL, texCoords: var array[4, GLfloat]): TextureRef =
44 | result = getTextureQuad(ai.images[ai.currentFrame], gl, texCoords)
45 |
46 | proc size*(ai: AnimatedImage): Size = ai.images[ai.currentFrame].size()
47 |
--------------------------------------------------------------------------------
/rod/component/all_components.nim:
--------------------------------------------------------------------------------
1 | # This is needed to include the components into the compilation so they
2 | # register themselves
3 |
4 | import blink, particle_system, particle_helpers, camera, channel_levels,
5 | mesh_component, overlay, particle_emitter, solid,
6 | sprite, text_component, light, clipping_rect_component,
7 | blur_component, visual_modifier, tracer, trail, fxaa_post,
8 | color_balance_hls, gradient_fill, color_fill, tint, ae_composition,
9 | primitives/cube, primitives/sphere, primitives/icosphere, primitives/cone, mask,
10 | comp_ref, rti, vector_shape, animation_runner, nine_part_sprite
11 |
--------------------------------------------------------------------------------
/rod/component/animation_runner.nim:
--------------------------------------------------------------------------------
1 | import rod / [ rod_types, component, viewport, node ]
2 | import nimx / [ animation, animation_runner, window ]
3 |
4 | method init*(c: AnimationRunnerComponent) =
5 | c.runner = newAnimationRunner()
6 |
7 | method componentNodeWasAddedToSceneView*(c: AnimationRunnerComponent) =
8 | c.node.sceneView.addAnimationRunner(c.runner)
9 |
10 | method componentNodeWillBeRemovedFromSceneView*(c: AnimationRunnerComponent) =
11 | c.node.sceneView.removeAnimationRunner(c.runner)
12 |
13 | registerComponent(AnimationRunnerComponent)
14 |
--------------------------------------------------------------------------------
/rod/component/blink.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, image, composition, property_visitor]
2 | import rod/[rod_types, viewport, component ]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import json
5 |
6 | var blinkComposition = newComposition """
7 | uniform Image uMask;
8 | uniform Image uLight;
9 | uniform vec2 uLightSize;
10 | uniform vec4 uFromRect;
11 | uniform float uLightPos;
12 | uniform float uScale;
13 |
14 | void compose() {
15 | vec2 destuv = (vPos - bounds.xy) / bounds.zw;
16 | vec2 duv = uMask.texCoords.zw - uMask.texCoords.xy;
17 | vec2 srcxy = uMask.texCoords.xy + duv * uFromRect.xy;
18 | vec2 srczw = uMask.texCoords.xy + duv * uFromRect.zw;
19 | vec2 uv = srcxy + (srczw - srcxy) * destuv;
20 | vec4 mask = texture2D(uMask.tex, uv);
21 |
22 | vec2 lightdestuv = (vPos - vec2(uLightPos, 0.0)) / uLightSize;
23 | vec2 lightduv = uLight.texCoords.zw - uLight.texCoords.xy;
24 | vec2 lightsrcxy = uLight.texCoords.xy + lightduv * uFromRect.xy;
25 | vec2 lightsrczw = uLight.texCoords.xy + lightduv * uFromRect.zw;
26 | vec2 lightuv = lightsrcxy + (lightsrczw - lightsrcxy) * lightdestuv;
27 |
28 | float rect_alpha = 1.0;
29 | if (lightuv.x < uLight.texCoords.x || lightuv.x > uLight.texCoords.z || lightuv.y < uLight.texCoords.y || lightuv.y > uLight.texCoords.w) {
30 | rect_alpha = 0.0;
31 | }
32 | vec4 light = texture2D(uLight.tex, lightuv);
33 |
34 | gl_FragColor = light;
35 | gl_FragColor.a *= mask.a * step(0.0, lightuv.x) * step(0.0, uLight.texCoords.z - lightuv.x) * rect_alpha;
36 | }
37 | """
38 |
39 | type Blink* = ref object of RenderComponent
40 | mask: Image
41 | light: Image
42 | currLightPos: float
43 | remainingTime: float
44 | period: float32
45 | speed: float32
46 |
47 | Blink.properties:
48 | mask
49 | light
50 | period
51 | speed
52 |
53 | method init(b: Blink) =
54 | b.speed = 1.0
55 | b.period = 2.0
56 | b.remainingTime = b.period
57 |
58 | method draw*(b: Blink) =
59 | if b.mask.isNil or b.light.isNil:
60 | return
61 |
62 | let c = currentContext()
63 | var r: Rect
64 | r.size = b.mask.size
65 | var fr = newRect(0, 0, 1, 1)
66 | let scale = b.mask.size.width / b.light.size.width
67 |
68 | let dt = getDeltaTime()
69 | b.currLightPos += b.speed * dt
70 | b.remainingTime -= dt
71 | if b.remainingTime <= 0.0:
72 | b.remainingTime = b.period
73 | b.currLightPos = 0.0
74 |
75 | blinkComposition.draw r:
76 | setUniform("uMask", b.mask)
77 | setUniform("uLight", b.light)
78 | setUniform("uLightSize", b.light.size)
79 | setUniform("uFromRect", fr)
80 | setUniform("uLightPos", b.currLightPos )
81 | setUniform("uScale", scale)
82 |
83 | method getBBox*(b: Blink): BBox =
84 | let img = b.mask
85 | if not img.isNil:
86 | result.maxPoint = newVector3(0, 0, 0.0)
87 | result.minPoint = newVector3(img.size.width, img.size.height, 0.0)
88 |
89 | genSerializationCodeForComponent(Blink)
90 |
91 | method visitProperties*(b: Blink, p: var PropertyVisitor) =
92 | p.visitProperty("mask", b.mask)
93 | p.visitProperty("light", b.light)
94 | p.visitProperty("speed", b.speed)
95 | p.visitProperty("period", b.period)
96 |
97 | registerComponent(Blink)
98 |
--------------------------------------------------------------------------------
/rod/component/camera.nim:
--------------------------------------------------------------------------------
1 | import nimx / [matrixes, types, context, property_visitor, view]
2 | import rod / [component, rod_types, node]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import math
5 |
6 | export CameraProjection
7 | export Camera
8 |
9 | Camera.properties:
10 | projectionMode
11 | zNear
12 | zFar
13 | fov
14 | viewportSize
15 |
16 | method init*(c: Camera) =
17 | c.projectionMode = cpPerspective
18 | c.zNear = 1
19 | c.zFar = 10000
20 | c.fov = 30
21 |
22 | proc calculateOrthoData(c: Camera): tuple[top, bottom, left, right: float] =
23 | let absBounds = c.node.sceneView.convertRectToWindow(c.node.sceneView.bounds)
24 | var winSize = absBounds.size
25 | if not c.node.sceneView.window.isNil:
26 | winSize = c.node.sceneView.window.bounds.size
27 |
28 | let cy = absBounds.y + absBounds.height / 2
29 | let cx = absBounds.x + absBounds.width / 2
30 |
31 | var logicalSize = c.viewportSize
32 | if logicalSize == zeroSize:
33 | logicalSize = absBounds.size
34 | c.viewportSize = logicalSize
35 | let k = absBounds.height / logicalSize.height
36 | result.top = -cy / k
37 | result.bottom = (winSize.height - cy) / k
38 | result.left = -cx / k
39 | result.right = (winSize.width - cx) / k
40 |
41 | proc getFrustum*(c: Camera): Frustum =
42 | let d = c.calculateOrthoData()
43 | let frustumOffset = -10.0
44 | let wt = c.node.worldTransform()
45 | result.minPoint = wt * newVector3(d.left + frustumOffset, d.top + frustumOffset, 0.0)
46 | result.maxPoint = wt * newVector3(d.right - frustumOffset, d.bottom - frustumOffset, 0.0)
47 |
48 | proc getProjectionMatrix*(c: Camera, viewportBounds: Rect, mat: var Transform3D) =
49 | doAssert(not c.node.sceneView.isNil)
50 | let absBounds = c.node.sceneView.convertRectToWindow(c.node.sceneView.bounds)
51 | var winSize = absBounds.size
52 | if not c.node.sceneView.window.isNil:
53 | winSize = c.node.sceneView.window.bounds.size
54 |
55 | let cy = absBounds.y + absBounds.height / 2
56 | let cx = absBounds.x + absBounds.width / 2
57 |
58 | case c.projectionMode
59 | of cpOrtho:
60 | let d = c.calculateOrthoData()
61 | mat.ortho(d.left, d.right, d.bottom, d.top, c.zNear, c.zFar)
62 |
63 | of cpPerspective:
64 | let top = -cy
65 | let bottom = winSize.height - cy
66 | let left = -cx
67 | let right = winSize.width - cx
68 |
69 | let angle = degToRad(c.fov) / 2.0
70 | let Z = absBounds.height / 2.0 / tan(angle)
71 |
72 | # near plane space
73 | let nLeft = c.zNear * left / Z
74 | let nRight = c.zNear * right / Z
75 | let nTop = c.zNear * top / Z
76 | let nBottom = c.zNear * bottom / Z
77 |
78 | mat.frustum(nLeft, nRight, -nBottom, -nTop, c.zNear, c.zFar)
79 |
80 | method visitProperties*(c: Camera, p: var PropertyVisitor) =
81 | p.visitProperty("zNear", c.zNear)
82 | p.visitProperty("zFar", c.zFar)
83 | p.visitProperty("fov", c.fov)
84 | p.visitProperty("vp size", c.viewportSize)
85 | p.visitProperty("projMode", c.projectionMode)
86 |
87 | genSerializationCodeForComponent(Camera)
88 | registerComponent(Camera)
89 |
--------------------------------------------------------------------------------
/rod/component/channel_levels.nim:
--------------------------------------------------------------------------------
1 | import nimx / [types, context, composition, portable_gl, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod / component
4 | import rod / tools / serializer
5 | import json
6 |
7 | type ChannelLevels* = ref object of RenderComponent
8 | inWhite*, inBlack*, inGamma*, outWhite*, outBlack*: Coord
9 | inWhiteV*, inBlackV*, inGammaV*, outWhiteV*, outBlackV*: Vector3
10 | active: bool
11 |
12 | ChannelLevels.properties:
13 | inWhite
14 | inBlack
15 | inGamma
16 | outWhite
17 | outBlack
18 | inWhiteV
19 | inBlackV
20 | inGammaV
21 | outWhiteV
22 | outBlackV
23 |
24 | var levelsPostEffect = newPostEffect("""
25 | vec3 colorPow(vec3 i, vec3 p) {
26 | return vec3(pow(i.r, p.r), pow(i.g, p.g), pow(i.b, p.b));
27 | }
28 |
29 | vec3 colorPow(vec3 i, float p) {
30 | return vec3(pow(i.r, p), pow(i.g, p), pow(i.b, p));
31 | }
32 |
33 | void channelLevels(vec3 inWhiteV, vec3 inBlackV, vec3 inGammaV, vec3 outWhiteV,
34 | vec3 outBlackV, float inWhite, float inBlack, float inGamma, float outWhite, float outBlack) {
35 | vec3 inPixel = gl_FragColor.rgb;
36 | inPixel = colorPow(clamp((inPixel - inBlackV) / (inWhiteV - inBlackV), 0.0, 1.0), 1.0 / inGammaV) * (outWhiteV - outBlackV) + outBlackV;
37 | inPixel = colorPow(clamp((inPixel - inBlack) / (inWhite - inBlack), 0.0, 1.0), 1.0 / inGamma) * (outWhite - outBlack) + outBlack;
38 |
39 | gl_FragColor.rgb = inPixel;
40 | }
41 | """, "channelLevels", ["vec3", "vec3", "vec3", "vec3", "vec3", "float", "float", "float", "float", "float"])
42 |
43 | # Dirty hack to optimize out extra drawing:
44 | template `~==`(f1, f2: float): bool = (f1 > f2 - 0.2 and f1 < f2 + 0.2)
45 | proc `~==`(v: Vector3, f2: float): bool {.inline.} = v[0] ~== f2 and v[1] ~== f2 and v[2] ~== f2
46 |
47 | template areValuesNormal(c: ChannelLevels): bool =
48 | c.inWhiteV ~== 1 and c.inWhite ~== 1 and
49 | c.inBlackV ~== 1 and c.inBlack ~== 0 and
50 | c.inGammaV ~== 1 and c.inGamma ~== 1 and
51 | c.outWhiteV ~== 1 and c.outWhite ~== 1 and
52 | c.outBlackV ~== 1 and c.outBlack ~== 0
53 |
54 | method init*(c: ChannelLevels) =
55 | c.inWhiteV = newVector3(1, 1, 1)
56 | c.inBlackV = newVector3(0, 0, 0)
57 | c.inGammaV = newVector3(1, 1, 1)
58 | c.outWhiteV = newVector3(1, 1, 1)
59 | c.outBlackV = newVector3(0, 0, 0)
60 |
61 | c.inWhite = 1
62 | c.inBlack = 0
63 | c.inGamma = 1
64 | c.outWhite = 1
65 | c.outBlack = 0
66 |
67 | method deserialize*(c: ChannelLevels, j: JsonNode, s: Serializer) =
68 | var v = j{"inWhiteV"}
69 | if not v.isNil:
70 | c.inWhiteV = newVector3(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
71 | c.inWhite = j["inWhite"].getFloat()
72 |
73 | v = j{"inBlackV"}
74 | if not v.isNil:
75 | c.inBlackV = newVector3(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
76 | c.inBlack = j["inBlack"].getFloat()
77 |
78 | v = j{"inGammaV"}
79 | if not v.isNil:
80 | c.inGammaV = newVector3(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
81 | c.inGamma = j["inGamma"].getFloat()
82 |
83 | v = j{"outWhiteV"}
84 | if not v.isNil:
85 | c.outWhiteV = newVector3(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
86 | c.outWhite = j["outWhite"].getFloat()
87 |
88 | v = j{"outBlackV"}
89 | if not v.isNil:
90 | c.outBlackV = newVector3(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
91 | c.outBlack = j["outBlack"].getFloat()
92 |
93 | method beforeDraw*(c: ChannelLevels, index: int): bool =
94 | c.active = not c.areValuesNormal()
95 | if c.active:
96 | pushPostEffect(levelsPostEffect, c.inWhiteV, c.inBlackV, c.inGammaV, c.outWhiteV, c.outBlackV, c.inWhite, c.inBlack, c.inGamma, c.outWhite, c.outBlack)
97 |
98 | method afterDraw*(c: ChannelLevels, index: int) =
99 | if c.active:
100 | popPostEffect()
101 |
102 | method serialize*(c: ChannelLevels, s: Serializer): JsonNode =
103 | result = newJObject()
104 |
105 | result.add("inWhiteV", s.getValue(c.inWhiteV))
106 | result.add("inBlackV", s.getValue(c.inBlackV))
107 | result.add("inGammaV", s.getValue(c.inGammaV))
108 | result.add("outWhiteV", s.getValue(c.outWhiteV))
109 | result.add("outBlackV", s.getValue(c.outBlackV))
110 |
111 | result.add("inWhite", s.getValue(c.inWhite))
112 | result.add("inBlack", s.getValue(c.inBlack))
113 | result.add("inGamma", s.getValue(c.inGamma))
114 | result.add("outWhite", s.getValue(c.outWhite))
115 | result.add("outBlack", s.getValue(c.outBlack))
116 |
117 | method visitProperties*(c: ChannelLevels, p: var PropertyVisitor) =
118 | p.visitProperty("inWhiteV", c.inWhiteV)
119 | p.visitProperty("inBlackV", c.inBlackV)
120 | p.visitProperty("inGammaV", c.inGammaV)
121 | p.visitProperty("outWhiteV", c.outWhiteV)
122 | p.visitProperty("outBlackV", c.outBlackV)
123 |
124 | p.visitProperty("inWhite", c.inWhite)
125 | p.visitProperty("inBlack", c.inBlack)
126 | p.visitProperty("inGamma", c.inGamma)
127 | p.visitProperty("outWhite", c.outWhite)
128 | p.visitProperty("outBlack", c.outBlack)
129 |
130 | p.visitProperty("redInWhite", c.inWhiteV[0], {pfAnimatable})
131 | p.visitProperty("greenInWhite", c.inWhiteV[1], {pfAnimatable})
132 | p.visitProperty("blueInWhite", c.inWhiteV[2], {pfAnimatable})
133 |
134 | p.visitProperty("redInBlack", c.inBlackV[0], {pfAnimatable})
135 | p.visitProperty("greenInBlack", c.inBlackV[1], {pfAnimatable})
136 | p.visitProperty("blueInBlack", c.inBlackV[2], {pfAnimatable})
137 |
138 | p.visitProperty("redInGamma", c.inGammaV[0], {pfAnimatable})
139 | p.visitProperty("greenInGamma", c.inGammaV[1], {pfAnimatable})
140 | p.visitProperty("blueInGamma", c.inGammaV[2], {pfAnimatable})
141 |
142 | p.visitProperty("redOutWhite", c.outWhiteV[0], {pfAnimatable})
143 | p.visitProperty("greenOutWhite", c.outWhiteV[1], {pfAnimatable})
144 | p.visitProperty("blueOutWhite", c.outWhiteV[2], {pfAnimatable})
145 |
146 | p.visitProperty("redOutBlack", c.outBlackV[0], {pfAnimatable})
147 | p.visitProperty("greenOutBlack", c.outBlackV[1], {pfAnimatable})
148 | p.visitProperty("blueOutBlack", c.outBlackV[2], {pfAnimatable})
149 |
150 | genSerializationCodeForComponent(ChannelLevels)
151 | registerComponent(ChannelLevels, "Effects")
152 |
--------------------------------------------------------------------------------
/rod/component/clipping_rect_component.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, composition, portable_gl, view, property_visitor]
2 | import rod/[ node, viewport, component, tools/serializer, rod_types]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import json
5 | import opengl
6 |
7 |
8 | const clippingRectWithScissors = true
9 |
10 | type ClippingRectComponent* = ref object of RenderComponent
11 | clippingRect*: Rect
12 |
13 | ClippingRectComponent.properties:
14 | clippingRect:
15 | serializationKey: "rect"
16 |
17 | when not clippingRectWithScissors:
18 | var clippingRectPostEffect = newPostEffect("""
19 | uniform vec2 uTopLeft;
20 | uniform vec2 uBottomRight;
21 | uniform vec2 viewportSize;
22 |
23 | float insideBox(vec2 v, vec2 bottomLeft, vec2 topRight) {
24 | vec2 s = step(bottomLeft, v) - step(topRight, v);
25 | return s.x * s.y;
26 | }
27 |
28 | void clipRect() {
29 | vec2 pos = gl_FragCoord.xy;
30 | pos.y = viewportSize.y - pos.y;
31 | gl_FragColor.a *= insideBox(pos, uTopLeft, uBottomRight);
32 | }
33 | """, "clipRect")
34 |
35 | import rod/tools/debug_draw
36 |
37 | proc debugDraw(cl: ClippingRectComponent, rect: Rect) =
38 | let gl = currentContext().gl
39 | gl.disable(gl.DEPTH_TEST)
40 | DDdrawRect(rect, newColor(1.0, 0.2, 0.2, 1.0))
41 | gl.disable(gl.DEPTH_TEST)
42 |
43 | method draw*(cl: ClippingRectComponent) =
44 | let tl = cl.clippingRect.minCorner()
45 | let br = cl.clippingRect.maxCorner()
46 | let tlv = newVector3(tl.x, tl.y)
47 | let brv = newVector3(br.x, br.y)
48 |
49 | let sv = cl.node.sceneView
50 | let tlvw = sv.worldToScreenPoint(cl.node.localToWorld(tlv))
51 | let brvw = sv.worldToScreenPoint(cl.node.localToWorld(brv))
52 |
53 | let tlp = sv.convertPointToWindow(newPoint(tlvw.x, tlvw.y))
54 | let brp = sv.convertPointToWindow(newPoint(brvw.x, brvw.y))
55 |
56 | when clippingRectWithScissors:
57 | let gl = currentContext().gl
58 | gl.enable(gl.SCISSOR_TEST)
59 | var pr = 1.0'f32
60 | var b: Rect
61 | if sv.window.isNil:
62 | b = sv.bounds
63 | else:
64 | pr = sv.window.viewportPixelRatio
65 | b = sv.window.bounds
66 |
67 | var x = GLint(tlp.x * pr)
68 | var y = GLint((b.height - brp.y) * pr)
69 | var w = GLsizei((brp.x - tlp.x) * pr)
70 | var h = GLSizei((brp.y - tlp.y) * pr)
71 | if w >= 0 and h >= 0:
72 | gl.scissor(x, y, w, h)
73 |
74 | for c in cl.node.children: c.recursiveDraw()
75 | gl.disable(gl.SCISSOR_TEST)
76 |
77 | else:
78 | pushPostEffect clippingRectPostEffect:
79 | setUniform("uTopLeft", tl2)
80 | setUniform("uBottomRight", br2)
81 | setUniform("viewportSize", vpSize)
82 |
83 | for c in cl.node.children: c.recursiveDraw()
84 |
85 | popPostEffect()
86 |
87 | if cl.node.sceneView.editing:
88 | cl.debugDraw(cl.clippingRect)
89 |
90 | method isPosteffectComponent*(c: ClippingRectComponent): bool = true
91 |
92 | method getBBox*(c: ClippingRectComponent): BBox =
93 | result.minPoint = newVector3(c.clippingRect.x, c.clippingRect.y, 0.0)
94 | result.maxPoint = newVector3(c.clippingRect.width, c.clippingRect.height, 0.0)
95 |
96 | method visitProperties*(cl: ClippingRectComponent, p: var PropertyVisitor) =
97 | p.visitProperty("rect", cl.clippingRect)
98 |
99 | method serialize*(c: ClippingRectComponent, s: Serializer): JsonNode =
100 | result = newJObject()
101 | result.add("rect", s.getValue(c.clippingRect))
102 |
103 | method deserialize*(c: ClippingRectComponent, j: JsonNode, s: Serializer) =
104 | s.deserializeValue(j, "rect", c.clippingRect)
105 |
106 | genSerializationCodeForComponent(ClippingRectComponent)
107 |
108 | registerComponent(ClippingRectComponent)
109 |
--------------------------------------------------------------------------------
/rod/component/color_balance_hls.nim:
--------------------------------------------------------------------------------
1 | import nimx / [types, context, composition, portable_gl, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod / [ component, tools/serializer ]
4 | import json
5 |
6 | type ColorBalanceHLS* = ref object of RenderComponent
7 | hue*: float32
8 | saturation*: float32
9 | lightness*: float32
10 | hlsMin*: float32
11 | hlsMax*: float32
12 | enabled: bool
13 |
14 | # This effect is borrowed from https://github.com/greggman/hsva-unity / HSL version
15 |
16 | ColorBalanceHLS.properties:
17 | hue
18 | saturation
19 | lightness
20 | hlsMin
21 | hlsMax
22 |
23 | var effect = newPostEffect("""
24 | float cbhls_effect_Epsilon = 1e-10;
25 |
26 | vec3 cbhls_effect_rgb2hcv(vec3 RGB) {
27 | // Based on work by Sam Hocevar and Emil Persson
28 | vec4 P = mix(vec4(RGB.bg, -1.0, 2.0/3.0), vec4(RGB.gb, 0.0, -1.0/3.0), step(RGB.b, RGB.g));
29 | vec4 Q = mix(vec4(P.xyw, RGB.r), vec4(RGB.r, P.yzx), step(P.x, RGB.r));
30 | float C = Q.x - min(Q.w, Q.y);
31 | float H = abs((Q.w - Q.y) / (6.0 * C + cbhls_effect_Epsilon) + Q.z);
32 | return vec3(H, C, Q.x);
33 | }
34 |
35 | vec3 cbhls_effect_rgb2hsl(vec3 RGB) {
36 | vec3 HCV = cbhls_effect_rgb2hcv(RGB);
37 | float L = HCV.z - HCV.y * 0.5;
38 | float S = HCV.y / (1.0 - abs(L * 2.0 - 1.0) + cbhls_effect_Epsilon);
39 | return vec3(HCV.x, S, L);
40 | }
41 |
42 | vec3 cbhls_effect_hsl2rgb(vec3 c) {
43 | c = vec3(fract(c.x), clamp(c.yz, 0.0, 1.0));
44 | vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
45 | return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
46 | }
47 |
48 | void cbhls_effect(float hue, float saturation, float lightness, float hlsMin, float hlsMax) {
49 | vec3 hsl = cbhls_effect_rgb2hsl(gl_FragColor.rgb);
50 | vec3 adjustment = vec3(hue, saturation, lightness);
51 | adjustment.xy *= step(0.001, hsl.x + hsl.y);
52 | float affectMult = step(hlsMin, hsl.r) * step(hsl.r, hlsMax);
53 | gl_FragColor.rgb = cbhls_effect_hsl2rgb(hsl + adjustment * affectMult);
54 | }
55 | """, "cbhls_effect", ["float", "float", "float", "float", "float"])
56 |
57 | method init*(c: ColorBalanceHLS) =
58 | procCall c.Component.init()
59 | c.hlsMax = 1.0
60 |
61 | # Dirty hack to optimize out extra drawing:
62 | template `~==`(f1, f2: float32): bool = (f1 > f2 - 0.02 and f1 < f2 + 0.02)
63 |
64 | template areValuesNormal(c: ColorBalanceHLS): bool =
65 | c.hue ~== 0 and c.saturation ~== 0 and c.lightness ~== 0
66 |
67 | method deserialize*(c: ColorBalanceHLS, j: JsonNode, s: Serializer) =
68 | c.hue = j["hue"].getFloat()
69 | c.saturation = j["saturation"].getFloat()
70 | c.lightness = j["lightness"].getFloat()
71 | c.hlsMin = j{"hlsMin"}.getFloat()
72 | c.hlsMax = j{"hlsMax"}.getFloat(1.0)
73 |
74 | method serialize*(c: ColorBalanceHLS, s: Serializer): JsonNode =
75 | result = newJObject()
76 | result.add("hue", s.getValue(c.hue))
77 | result.add("saturation", s.getValue(c.saturation))
78 | result.add("lightness", s.getValue(c.lightness))
79 | result.add("hlsMin", s.getValue(c.hlsMin))
80 | result.add("hlsMax", s.getValue(c.hlsMax))
81 |
82 | method beforeDraw*(c: ColorBalanceHLS, index: int): bool =
83 | c.enabled = not c.areValuesNormal()
84 | if c.enabled:
85 | pushPostEffect(effect, c.hue, c.saturation, c.lightness, c.hlsMin, c.hlsMax)
86 |
87 | method afterDraw*(c: ColorBalanceHLS, index: int) =
88 | if c.enabled:
89 | popPostEffect()
90 |
91 | method visitProperties*(c: ColorBalanceHLS, p: var PropertyVisitor) =
92 | p.visitProperty("hue", c.hue)
93 | p.visitProperty("saturation", c.saturation)
94 | p.visitProperty("lightness", c.lightness)
95 | p.visitProperty("hlsMin", c.hlsMin)
96 | p.visitProperty("hlsMax", c.hlsMax)
97 |
98 | genSerializationCodeForComponent(ColorBalanceHLS)
99 | registerComponent(ColorBalanceHLS, "Effects")
100 |
--------------------------------------------------------------------------------
/rod/component/color_fill.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, composition, portable_gl, property_visitor]
2 | import rod / [component, tools/serializer]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import json
5 |
6 | type ColorFill* = ref object of RenderComponent
7 | color*: Color
8 |
9 | ColorFill.properties:
10 | color
11 |
12 | var effect = newPostEffect("""
13 | void color_fill_effect(vec4 color, float dummy) {
14 | color.a *= gl_FragColor.a;
15 | gl_FragColor = color;
16 | }
17 | """, "color_fill_effect", ["vec4", "float"])
18 |
19 | method deserialize*(c: ColorFill, j: JsonNode, s: Serializer) =
20 | var v = j["color"]
21 | c.color = newColor(v[0].getFloat(), v[1].getFloat(), v[2].getFloat(), v[3].getFloat())
22 |
23 | method beforeDraw*(c: ColorFill, index: int): bool =
24 | const dummyUniform = 0.0'f32 # This unpleasantness is originated from the fact
25 | # that new `pushPostEffect` is conflicting with the
26 | # old one when number of uniforms is 1.
27 | # Should be cleaned up when old `pushPostEffect` is removed
28 | pushPostEffect(effect, c.color, dummyUniform)
29 |
30 | method afterDraw*(c: ColorFill, index: int) =
31 | popPostEffect()
32 |
33 | method visitProperties*(c: ColorFill, p: var PropertyVisitor) =
34 | p.visitProperty("color", c.color)
35 |
36 | genSerializationCodeForComponent(ColorFill)
37 |
38 | registerComponent(ColorFill, "Effects")
39 |
--------------------------------------------------------------------------------
/rod/component/comp_ref.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, matrixes, property_visitor ]
2 | import rod / [ node, rod_types, component, tools/serializer ]
3 | import rod / utils / [property_desc, serialization_codegen ]
4 | import rod/component/nine_part_sprite
5 | import json
6 |
7 |
8 | type CompRef* = ref object of ScriptComponent
9 | size*: Size
10 | path: string
11 | refNode: Node
12 |
13 | CompRef.properties:
14 | size
15 | path
16 |
17 | proc setSize(n: Node, sz: Size) =
18 | for c in n.components:
19 | if c of NinePartSprite:
20 | let s = NinePartSprite(c)
21 | s.size = sz
22 | for c in n.children:
23 | c.setSize(sz)
24 |
25 | proc awake(c: CompRef) =
26 | let n = newNodeWithResource(c.path)
27 | n.setSize(c.size)
28 | n.isSerializable = false
29 | c.node.addChild(n)
30 | c.refNode = n
31 |
32 | proc setSize*(c: CompRef, s: Size)=
33 | if not c.refNode.isNil:
34 | c.refNode.setSize(s)
35 |
36 | method deserialize*(s: CompRef, j: JsonNode, serializer: Serializer) =
37 | let v = j{"size"}
38 | if not v.isNil:
39 | s.size = newSize(v[0].getFloat(), v[1].getFloat())
40 | s.path = j["path"].str
41 | s.awake()
42 |
43 | genSerializationCodeForComponent(CompRef)
44 |
45 | method getBBox*(s: CompRef): BBox =
46 | result.minPoint = newVector3(0.0, 0.0, 0.0)
47 | result.maxPoint = newVector3(s.size.width, s.size.height, 0.0)
48 |
49 | method visitProperties*(c: CompRef, p: var PropertyVisitor) =
50 | p.visitProperty("size", c.size)
51 | c.setSize(c.size)
52 |
53 | method serialize*(c: CompRef, s: Serializer): JsonNode =
54 | result = newJObject()
55 | result.add("size", s.getValue(c.size))
56 | result.add("path", s.getValue(c.path))
57 |
58 | registerComponent(CompRef)
59 |
--------------------------------------------------------------------------------
/rod/component/gradient_fill.nim:
--------------------------------------------------------------------------------
1 | import nimx/[view, context, matrixes, composition, portable_gl, property_visitor ]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[node, viewport, component, tools/serializer]
4 |
5 | import json
6 |
7 | var effectLinearLocal = newPostEffect("""
8 | void grad_fill_effect_linear_local(vec2 gradientStartPos, vec2 gradientEndPos, vec4 startColor, vec4 endColor) {
9 | float alpha = atan( -gradientEndPos.y + gradientStartPos.y, gradientEndPos.x - gradientStartPos.x );
10 | float gradientStartPosRotatedX = gradientStartPos.x*cos(alpha) - gradientStartPos.y*sin(alpha);
11 | float gradientEndPosRotatedX = gradientEndPos.x*cos(alpha) - gradientEndPos.y*sin(alpha);
12 | float d = gradientEndPosRotatedX - gradientStartPosRotatedX;
13 | float y = vPos.y;
14 | float x = vPos.x;
15 | float xLocRotated = x*cos( alpha ) - y*sin( alpha );
16 | vec4 gradientColor = mix(startColor, endColor, smoothstep( gradientStartPosRotatedX, gradientStartPosRotatedX + d, xLocRotated ) );
17 |
18 | gl_FragColor.rgb = mix(gl_FragColor, gradientColor, gradientColor.a).rgb;
19 | }
20 | """, "grad_fill_effect_linear_local", ["vec2", "vec2", "vec4", "vec4"])
21 |
22 | var effectLinear = newPostEffect("""
23 | void grad_fill_effect_linear(vec2 startPoint, vec2 diff, vec4 startColor, vec4 endColor) {
24 | float s = dot(gl_FragCoord.xy-startPoint, diff) / dot(diff, diff);
25 | vec4 color = mix(startColor, endColor, s);
26 | color.a *= gl_FragColor.a;
27 | gl_FragColor = color;
28 | }
29 | """, "grad_fill_effect_linear", ["vec2", "vec2", "vec4", "vec4"])
30 |
31 | var effectRadialLocal = newPostEffect("""
32 | void grad_fill_effect_radial_local(vec2 center, float radius, vec4 startColor, vec4 endColor) {
33 | float dist = distance(center, vPos.xy);
34 | float d = smoothstep(0.0, 1.0, dist / radius);
35 | vec4 color = mix(startColor, endColor, d);
36 |
37 | gl_FragColor.rgb = mix(gl_FragColor, color, color.a).rgb;
38 | }
39 | """, "grad_fill_effect_radial_local", ["vec2", "float", "vec4", "vec4"])
40 |
41 | var effectRadial = newPostEffect("""
42 | void grad_fill_effect_radial(vec2 center, float radius, vec4 startColor, vec4 endColor) {
43 | float dist = distance(center, gl_FragCoord.xy);
44 | float d = dist / radius;
45 | vec4 color = mix(startColor, endColor, d);
46 | color.a *= gl_FragColor.a;
47 | gl_FragColor = color;
48 | }
49 | """, "grad_fill_effect_radial", ["vec2", "float", "vec4", "vec4"])
50 |
51 | type RampShape* = enum
52 | LinearRamp
53 | RadialRamp
54 |
55 | type GradientFill* = ref object of RenderComponent
56 | startPoint*: Point
57 | endPoint*: Point
58 | startColor*: Color
59 | endColor*: Color
60 | shape*: RampShape
61 | localCoords*: bool
62 |
63 | GradientFill.properties:
64 | startPoint
65 | endPoint
66 | startColor
67 | endColor
68 | shape
69 | localCoords
70 |
71 | method serialize*(gf: GradientFill, serealizer: Serializer): JsonNode =
72 | result = newJObject()
73 | result.add("startPoint", serealizer.getValue(gf.startPoint))
74 | result.add("endPoint", serealizer.getValue(gf.endPoint))
75 | result.add("startColor", serealizer.getValue(gf.startColor))
76 | result.add("endColor", serealizer.getValue(gf.endColor))
77 | result.add("shape", serealizer.getValue(gf.shape))
78 | result.add("localCoords", serealizer.getValue(gf.localCoords))
79 |
80 | method deserialize*(gf: GradientFill, j: JsonNode, serealizer: Serializer) =
81 | serealizer.deserializeValue(j, "startPoint", gf.startPoint)
82 | serealizer.deserializeValue(j, "endPoint", gf.endPoint)
83 | serealizer.deserializeValue(j, "startColor", gf.startColor)
84 | serealizer.deserializeValue(j, "endColor", gf.endColor)
85 | serealizer.deserializeValue(j, "shape", gf.shape)
86 | serealizer.deserializeValue(j, "localCoords", gf.localCoords)
87 |
88 | method beforeDraw*(gf: GradientFill, index: int): bool =
89 | var tlp: Point
90 | var brp: Point
91 |
92 | if gf.localCoords:
93 | tlp = gf.startPoint
94 | brp = gf.endPoint
95 | else:
96 | let tl = gf.startPoint
97 | let br = gf.endPoint
98 | let tlv = newVector3(tl.x, tl.y)
99 | let brv = newVector3(br.x, br.y)
100 |
101 | let sv = gf.node.sceneView
102 |
103 | var screenBounds = sv.bounds
104 | if not sv.window.isNil:
105 | screenBounds = sv.window.bounds
106 |
107 | let tlvw = sv.worldToScreenPoint(gf.node.localToWorld(tlv))
108 | let brvw = sv.worldToScreenPoint(gf.node.localToWorld(brv))
109 |
110 | tlp = sv.convertPointToWindow(newPoint(tlvw.x, tlvw.y))
111 | brp = sv.convertPointToWindow(newPoint(brvw.x, brvw.y))
112 |
113 | tlp.y = screenBounds.height - tlp.y
114 | brp.y = screenBounds.height - brp.y
115 |
116 | if gf.shape == RadialRamp:
117 | let radius = distanceTo(tlp, brp)
118 | if gf.localCoords:
119 | pushPostEffect(effectRadialLocal, tlp, radius, gf.startColor, gf.endColor)
120 | else:
121 | pushPostEffect(effectRadial, tlp, radius, gf.startColor, gf.endColor)
122 | else:
123 | let diff = brp - tlp
124 | if gf.localCoords:
125 | pushPostEffect(effectLinearLocal, tlp, brp, gf.startColor, gf.endColor)
126 | else:
127 | pushPostEffect(effectLinear, tlp, brp, gf.startColor, gf.endColor)
128 |
129 | method afterDraw*(gf: GradientFill, index: int) =
130 | popPostEffect()
131 |
132 | method visitProperties*(gf: GradientFill, p: var PropertyVisitor) =
133 | p.visitProperty("startPoint", gf.startPoint)
134 | p.visitProperty("startColor", gf.startColor)
135 | p.visitProperty("endPoint", gf.endPoint)
136 | p.visitProperty("endColor", gf.endColor)
137 | p.visitProperty("shape", gf.shape)
138 | p.visitProperty("localCoords", gf.localCoords)
139 |
140 | genSerializationCodeForComponent(GradientFill)
141 |
142 | registerComponent(GradientFill, "Effects")
143 |
--------------------------------------------------------------------------------
/rod/component/light.nim:
--------------------------------------------------------------------------------
1 | import rod/[rod_types, component, node, tools/serializer]
2 | import nimx/[types, matrixes, property_visitor]
3 | import json, tables, logging
4 |
5 | export LightSource
6 |
7 | proc addLightSource*(v: SceneView, ls: LightSource) =
8 | if v.lightSources.isNil():
9 | v.lightSources = newTable[string, LightSource]()
10 | if v.lightSources.len() < rod_types.maxLightsCount:
11 | v.lightSources[ls.node.name] = ls
12 | else:
13 | warn "Count of light sources is limited. Current count: ", rod_types.maxLightsCount
14 |
15 | proc removeLightSource*(v: SceneView, ls: LightSource) =
16 | if v.lightSources.isNil() or v.lightSources.len() <= 0:
17 | info "Current light sources count equals 0."
18 | else:
19 | v.lightSources.del(ls.node.name)
20 |
21 | proc `lightAmbient=`*(ls: LightSource, val: Coord) =
22 | ls.mLightAmbient = val
23 | ls.lightAmbientInited = true
24 | proc `lightDiffuse=`*(ls: LightSource, val: Coord) =
25 | ls.mLightDiffuse = val
26 | ls.lightDiffuseInited = true
27 | proc `lightSpecular=`*(ls: LightSource, val: Coord) =
28 | ls.mLightSpecular = val
29 | ls.lightSpecularInited = true
30 | proc `lightConstant=`*(ls: LightSource, val: Coord) =
31 | ls.mLightConstant = val
32 | ls.lightConstantInited = true
33 | proc `lightLinear=`*(ls: LightSource, val: Coord) =
34 | ls.mLightLinear = val
35 | ls.lightLinearInited = true
36 | proc `lightQuadratic=`*(ls: LightSource, val: Coord) =
37 | ls.mLightQuadratic = val
38 | ls.lightQuadraticInited = true
39 | proc `lightAttenuationInited=`*(ls: LightSource, val: bool) =
40 | ls.mLightAttenuationInited = val
41 | if ls.mLightAttenuationInited:
42 | ls.lightConstantInited = false
43 | ls.lightLinearInited = false
44 | ls.lightQuadraticInited = false
45 | else:
46 | ls.lightConstantInited = true
47 | ls.lightLinearInited = true
48 | ls.lightQuadraticInited = true
49 | proc `lightAttenuation=`*(ls: LightSource, val: Coord) =
50 | ls.mLightAttenuation = val
51 | proc `lightColor=`*(ls: LightSource, val: Color) =
52 | ls.mLightColor = val
53 |
54 | template lightAmbient*(ls: LightSource): Coord = ls.mLightAmbient
55 | template lightDiffuse*(ls: LightSource): Coord = ls.mLightDiffuse
56 | template lightSpecular*(ls: LightSource): Coord = ls.mLightSpecular
57 | template lightConstant*(ls: LightSource): Coord = ls.mLightConstant
58 | template lightLinear*(ls: LightSource): Coord = ls.mLightLinear
59 | template lightQuadratic*(ls: LightSource): Coord = ls.mLightQuadratic
60 | template lightAttenuation*(ls: LightSource): Coord = ls.mLightAttenuation
61 | template lightAttenuationInited*(ls: LightSource): bool = ls.mLightAttenuationInited
62 | template lightColor*(ls: LightSource): Color = ls.mLightColor
63 |
64 | proc setDefaultLightSource*(ls: LightSource) =
65 | ls.lightAmbient = 1.0
66 | ls.lightDiffuse = 1.0
67 | ls.lightSpecular = 1.0
68 | ls.lightConstant = 1.0
69 | ls.lightLinear = 0.000014
70 | ls.lightQuadratic = 0.00000007
71 | # ls.lightAttenuationInited = false
72 | ls.lightColor = newColor(1.0, 1.0, 1.0, 1.0)
73 | ls.lightAttenuation = 1.0
74 | ls.lightAttenuationInited = true
75 |
76 | method init*(ls: LightSource) =
77 | procCall ls.Component.init()
78 | ls.setDefaultLightSource()
79 |
80 | method componentNodeWasAddedToSceneView*(ls: LightSource) =
81 | ls.node.sceneView.addLightSource(ls)
82 |
83 | method componentNodeWillBeRemovedFromSceneView(ls: LightSource) =
84 | ls.node.sceneView.removeLightSource(ls)
85 |
86 | method getBBox*(ls: LightSource): BBox =
87 | result.minPoint = newVector3(-3, -3, -3)
88 | result.maxPoint = newVector3(3, 3, 3)
89 |
90 | method deserialize*(ls: LightSource, j: JsonNode, s: Serializer) =
91 | var v = j{"ambient"}
92 | if not v.isNil:
93 | ls.lightAmbient = v.getFloat()
94 |
95 | v = j{"diffuse"}
96 | if not v.isNil:
97 | ls.lightDiffuse = v.getFloat()
98 |
99 | v = j{"specular"}
100 | if not v.isNil:
101 | ls.lightSpecular = v.getFloat()
102 |
103 | v = j{"constant"}
104 | if not v.isNil:
105 | ls.lightConstant = v.getFloat()
106 |
107 | v = j{"linear"}
108 | if not v.isNil:
109 | ls.lightLinear = v.getFloat()
110 |
111 | v = j{"quadratic"}
112 | if not v.isNil:
113 | ls.lightQuadratic = v.getFloat()
114 |
115 | v = j{"is_precomp_attenuation"}
116 | if not v.isNil:
117 | ls.lightAttenuationInited = v.getBool()
118 |
119 | v = j{"attenuation"}
120 | if not v.isNil:
121 | ls.lightAttenuation = v.getFloat()
122 |
123 | v = j{"color"}
124 | if not v.isNil:
125 | ls.lightColor.r = v[0].getFloat()
126 | ls.lightColor.g = v[1].getFloat()
127 | ls.lightColor.b = v[2].getFloat()
128 | ls.lightColor.a = v[3].getFloat()
129 |
130 | method serialize*(c: LightSource, s: Serializer): JsonNode =
131 | result = newJObject()
132 | result.add("ambient", s.getValue(c.lightAmbient))
133 | result.add("diffuse", s.getValue(c.lightDiffuse))
134 | result.add("specular", s.getValue(c.lightSpecular))
135 | result.add("constant", s.getValue(c.lightConstant))
136 | result.add("linear", s.getValue(c.lightLinear))
137 | result.add("quadratic", s.getValue(c.lightQuadratic))
138 | result.add("is_precomp_attenuation", s.getValue(c.lightAttenuationInited))
139 | result.add("attenuation", s.getValue(c.lightAttenuation))
140 | result.add("color", s.getValue(c.lightColor))
141 |
142 | method visitProperties*(ls: LightSource, p: var PropertyVisitor) =
143 | p.visitProperty("ambient", ls.lightAmbient)
144 | p.visitProperty("diffuse", ls.lightDiffuse)
145 | p.visitProperty("specular", ls.lightSpecular)
146 | p.visitProperty("constant", ls.lightConstant)
147 | p.visitProperty("linear", ls.lightLinear)
148 | p.visitProperty("quadratic", ls.lightQuadratic)
149 |
150 | p.visitProperty("precomp_att", ls.lightAttenuation)
151 | p.visitProperty("use_precomp", ls.lightAttenuationInited)
152 |
153 | p.visitProperty("color", ls.lightColor)
154 |
155 | registerComponent(LightSource)
156 |
--------------------------------------------------------------------------------
/rod/component/nine_part_sprite.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, context, image, animation, property_visitor ]
2 | import rod / [rod_types, node, component, tools/serializer]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import rod/component/sprite
5 | import json
6 |
7 | type NinePartSprite* = ref object of Sprite
8 | mSize: Size
9 | segments: Vector4
10 |
11 | NinePartSprite.properties:
12 | mSize:
13 | serializationKey: "size"
14 | segments
15 |
16 | proc size*(s: NinePartSprite): Size =
17 | s.mSize
18 |
19 | proc `size=`*(s: NinePartSprite, v: Size) =
20 | s.mSize = v
21 |
22 | template marginLeft(s: NinePartSprite): float32 = s.segments.x
23 | template marginRight(s: NinePartSprite): float32 = s.segments.y
24 | template marginTop(s: NinePartSprite): float32 = s.segments.z
25 | template marginBottom(s: NinePartSprite): float32 = s.segments.w
26 |
27 | proc `image=`*(s: NinePartSprite, i: Image) =
28 | procCall s.Sprite.`image=`(i)
29 | s.segments.x = i.size.width * 0.4
30 | s.segments.y = i.size.width * 0.4
31 | s.segments.z = i.size.height * 0.4
32 | s.segments.w = i.size.height * 0.4
33 |
34 | s.size = i.size
35 |
36 | method getBBox*(s: NinePartSprite): BBox =
37 | let sz = s.size
38 | result.maxPoint = newVector3(sz.width + s.getOffset.x, sz.height + s.getOffset.y, 0.0)
39 | result.minPoint = newVector3(s.getOffset.x, s.getOffset.y, 0.0)
40 |
41 | method draw*(s: NinePartSprite) =
42 | let c = currentContext()
43 | let i = s.image
44 | if not i.isNil:
45 | var r: Rect
46 | r.origin = s.getOffset()
47 | r.size = s.size
48 | c.drawNinePartImage(i, r, s.marginLeft, s.marginTop, s.marginRight, s.marginBottom)
49 |
50 | # hack serializer/deserializer from sprite
51 | method serialize*(c: NinePartSprite, s: Serializer): JsonNode =
52 | result = procCall c.Sprite.serialize(s)
53 | var r2 = procCall c.Component.serialize(s)
54 | for k, v in r2:
55 | result[k] = v
56 |
57 | method deserialize*(c: NinePartSprite, j: JsonNode, s: Serializer) =
58 | procCall c.Sprite.deserialize(j, s)
59 | procCall c.Component.deserialize(j, s)
60 |
61 | genSerializationCodeForComponent(NinePartSprite)
62 | registerComponent(NinePartSprite)
63 |
64 | when defined(rodedit):
65 | type NinePartSegmentsAUX* = ref object
66 | segments*: Vector4
67 | image*: Image
68 | size*: Size
69 |
70 | proc segmentsAUX(s: NinePartSprite): NinePartSegmentsAUX =
71 | result = NinePartSegmentsAUX(segments: s.segments, image: s.image, size: s.size)
72 |
73 | proc `segmentsAUX=`*(s: NinePartSprite, v: NinePartSegmentsAUX) =
74 | s.segments = v.segments
75 |
76 | method visitProperties*(s: NinePartSprite, p: var PropertyVisitor) =
77 | p.visitProperty("image", s.image)
78 | p.visitProperty("size", s.size)
79 |
80 | when defined(rodedit):
81 | p.visitProperty("nine part", s.segmentsAUX)
82 |
--------------------------------------------------------------------------------
/rod/component/overlay.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, composition, portable_gl, matrixes]
2 | import rod / component
3 |
4 | type Overlay* = ref object of RenderComponent
5 |
6 | var overlayPostEffect = newPostEffect("""
7 |
8 | void overlay_effect(float spike, float spike1) {
9 | vec4 maskColor = gl_FragColor;
10 | gl_FragColor.rgba = vec4(maskColor.a);
11 | }
12 | """, "overlay_effect", ["float", "float"])
13 |
14 | method beforeDraw*(o: Overlay, index: int): bool =
15 | let gl = currentContext().gl
16 | gl.blendFunc(gl.DST_COLOR, gl.ONE)
17 |
18 | pushPostEffect(overlayPostEffect, 0.0, 0.0)
19 |
20 | method afterDraw*(o: Overlay, index: int) =
21 | let gl = currentContext().gl
22 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
23 | popPostEffect()
24 |
25 | method supportsNewSerialization*(cm: Overlay): bool = true
26 |
27 | registerComponent(Overlay, "Effects")
28 |
--------------------------------------------------------------------------------
/rod/component/primitives/cone.nim:
--------------------------------------------------------------------------------
1 | import nimx/[matrixes, types, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[vertex_data_info, component]
4 | import rod/component/[ mesh_component, material ]
5 | import rod/tools/serializer
6 | import opengl, math, json
7 |
8 | type ConeComponent* = ref object of MeshComponent
9 | mRadius1: float32
10 | mRadius2: float32
11 | mHeight: float32
12 | mSegments: int32
13 |
14 | ConeComponent.properties:
15 | mRadius1
16 | mRadius2
17 | mHeight
18 | mSegments
19 |
20 | proc fillBuffers(c: ConeComponent, vertCoords, texCoords, normals: var seq[float32], indices: var seq[GLushort]) =
21 | let angle_step = 2.0 * PI.float32 / c.mSegments.float32
22 | var angle = 0.0
23 | var v0, v1, v2, v3: Vector3
24 | var index_offset = 0
25 |
26 | # cone body
27 | while angle <= 2 * PI + angle_step:
28 | v0 = newVector(c.mRadius1 * cos(angle).float32, 0.0, c.mRadius1 * sin(angle))
29 | v1 = newVector(c.mRadius2 * cos(angle).float32, c.mHeight, c.mRadius2 * sin(angle))
30 | vertCoords.add(v0)
31 | vertCoords.add(v1)
32 |
33 | texCoords.add([0.0.float32, 0.0])
34 | texCoords.add([1.0.float32, 1.0])
35 |
36 | normals.add([cos(angle).float32, 0.0, sin(angle)])
37 | normals.add([cos(angle).float32, 0.0, sin(angle)])
38 |
39 | angle += angle_step
40 |
41 | for i in 0 ..< c.mSegments:
42 | indices.add([(2*i + 0).GLushort, (2*i + 1).GLushort, (2*i + 2).GLushort])
43 | indices.add([(2*i + 3).GLushort, (2*i + 2).GLushort, (2*i + 1).GLushort])
44 |
45 | index_offset = int(vertCoords.len() / 3)
46 |
47 | # # cap bottom
48 | # # central vertex
49 | vertCoords.add([0.0.float32, 0.0, 0.0])
50 | texCoords.add([0.0.float32, 0.0])
51 | normals.add([0.0.float32, -1.0, 0.0])
52 | angle = 0.0
53 |
54 | for i in 0 .. c.mSegments:
55 | vertCoords.add([c.mRadius1 * cos(angle).float32, 0.0, c.mRadius1 * sin(angle)])
56 | texCoords.add([0.0.float32, 0.0])
57 | normals.add([0.0.float32, -1.0, 0.0])
58 | angle += angle_step
59 |
60 | if i < c.mSegments:
61 | indices.add([index_offset.GLushort, (i + index_offset + 1).GLushort, (i + index_offset + 2).GLushort])
62 |
63 | index_offset = int(vertCoords.len() / 3)
64 |
65 | # cap top
66 | # central vertex
67 | vertCoords.add([0.0.float32, c.mHeight, 0.0])
68 | texCoords.add([0.0.float32, 0.0])
69 | normals.add([0.0.float32, 1.0, 0.0])
70 | angle = 0.0
71 |
72 | for i in 0 .. c.mSegments:
73 | vertCoords.add([c.mRadius2 * cos(angle).float32, c.mHeight, c.mRadius2 * sin(angle)])
74 | texCoords.add([0.0.float32, 0.0])
75 | normals.add([0.0.float32, 1.0, 0.0])
76 | angle += angle_step
77 |
78 | if i < c.mSegments:
79 | indices.add([index_offset.GLushort, (i + index_offset + 2).GLushort, (i + index_offset + 1).GLushort])
80 |
81 |
82 | method init*(c: ConeComponent) =
83 | procCall c.MeshComponent.init()
84 | c.mRadius1 = 1.0
85 | c.mRadius2 = 1.0
86 | c.mHeight = 4.0
87 | c.mSegments = 12
88 |
89 | c.material.ambient = newColor(1.0, 1.0, 1.0, 0.2)
90 | c.material.diffuse = newColor(1.0, 1.0, 1.0, 1.0)
91 |
92 |
93 | proc generateMesh*(c: ConeComponent) =
94 | let mesh = c
95 |
96 | var vertCoords = newSeq[float32]()
97 | var texCoords = newSeq[float32]()
98 | var normals = newSeq[float32]()
99 | var indices = newSeq[GLushort]()
100 |
101 | c.fillBuffers(vertCoords, texCoords, normals, indices)
102 |
103 | mesh.vboData.minCoord = newVector3(high(int).Coord, high(int).Coord, high(int).Coord)
104 | mesh.vboData.maxCoord = newVector3(low(int).Coord, low(int).Coord, low(int).Coord)
105 | mesh.vboData.vertInfo = newVertexInfoWithVertexData(vertCoords.len, texCoords.len, normals.len, 0)
106 |
107 | let stride = int32( mesh.vboData.vertInfo.stride / sizeof(GLfloat) )
108 | let size = int32(vertCoords.len * stride / 3)
109 | var vertexData = c.createVertexData(stride, size, vertCoords, texCoords, normals, @[])
110 |
111 | mesh.createVBO(indices, vertexData)
112 |
113 | template radius1*(cc: ConeComponent): float = c.mRadius1
114 | template `radius1=`*(cc: ConeComponent, v: float) =
115 | cc.mRadius1 = v
116 | cc.generateMesh()
117 |
118 | template radius2*(cc: ConeComponent): float = c.mRadius2
119 | template `radius2=`*(cc: ConeComponent, v: float) =
120 | cc.mRadius2 = v
121 | cc.generateMesh()
122 |
123 | template height*(cc: ConeComponent): float = c.mHeight
124 | template `height=`*(cc: ConeComponent, v: float) =
125 | cc.mHeight = v
126 | cc.generateMesh()
127 |
128 | template segments*(cc: ConeComponent): int = c.mSegments.int
129 | template `segments=`*(cc: ConeComponent, v: int) =
130 | cc.mSegments = v.int32
131 | cc.generateMesh()
132 |
133 | method componentNodeWasAddedToSceneView*(c: ConeComponent) =
134 | c.generateMesh()
135 |
136 | method deserialize*(c: ConeComponent, j: JsonNode, s: Serializer) =
137 | if j.isNil:
138 | return
139 |
140 | s.deserializeValue(j, "radius1", c.radius1)
141 | s.deserializeValue(j, "radius2", c.radius2)
142 | s.deserializeValue(j, "height", c.height)
143 | s.deserializeValue(j, "segments", c.segments)
144 |
145 | method serialize*(c: ConeComponent, s: Serializer): JsonNode =
146 | result = newJObject()
147 | result.add("radius1", s.getValue(c.radius1))
148 | result.add("radius2", s.getValue(c.radius2))
149 | result.add("height", s.getValue(c.height))
150 | result.add("segments", s.getValue(c.segments))
151 |
152 | method visitProperties*(c: ConeComponent, p: var PropertyVisitor) =
153 | p.visitProperty("radius1", c.radius1)
154 | p.visitProperty("radius2", c.radius2)
155 | p.visitProperty("height", c.height)
156 | p.visitProperty("segments", c.segments)
157 | procCall c.MeshComponent.visitProperties(p)
158 |
159 | genSerializationCodeForComponent(ConeComponent)
160 | registerComponent(ConeComponent, "Primitives")
161 |
--------------------------------------------------------------------------------
/rod/component/primitives/cube.nim:
--------------------------------------------------------------------------------
1 | import nimx/[matrixes, types, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[vertex_data_info, component]
4 | import rod/component/[ mesh_component, material ]
5 | import rod/tools/serializer
6 | import opengl, json
7 |
8 | type CubeComponent* = ref object of MeshComponent
9 | mSize: Vector3
10 |
11 | CubeComponent.properties:
12 | mSize
13 |
14 | proc fillVertexBuffers(vertCoords, texCoords, normals: var seq[float32], size: Vector3) =
15 | #front
16 | vertCoords.add([-size.x, -size.y, -size.z])
17 | vertCoords.add([-size.x, size.y, -size.z])
18 | vertCoords.add([ size.x, size.y, -size.z])
19 | vertCoords.add([ size.x, -size.y, -size.z])
20 |
21 | for i in 0 .. 3:
22 | normals.add([ 0.0f, 0.0f, -1.0f])
23 |
24 | texCoords.add([ 0.0f, 0.0f])
25 | texCoords.add([ 1.0f, 0.0f])
26 | texCoords.add([ 1.0f, 1.0f])
27 | texCoords.add([ 0.0f, 1.0f])
28 |
29 | #right
30 | vertCoords.add([ size.x, -size.y, -size.z])
31 | vertCoords.add([ size.x, size.y, -size.z])
32 | vertCoords.add([ size.x, size.y, size.z])
33 | vertCoords.add([ size.x, -size.y, size.z])
34 |
35 | for i in 0 .. 3:
36 | normals.add([ 1.0f, 0.0f, 0.0f])
37 |
38 | texCoords.add([ 0.0f, 0.0f])
39 | texCoords.add([ 1.0f, 0.0f])
40 | texCoords.add([ 1.0f, 1.0f])
41 | texCoords.add([ 0.0f, 1.0f])
42 |
43 | #back
44 | vertCoords.add([ size.x, -size.y, size.z])
45 | vertCoords.add([ size.x, size.y, size.z])
46 | vertCoords.add([-size.x, size.y, size.z])
47 | vertCoords.add([-size.x, -size.y, size.z])
48 |
49 | for i in 0 .. 3:
50 | normals.add([ 0.0f, 0.0f, 1.0f])
51 |
52 | texCoords.add([ 0.0f, 0.0f])
53 | texCoords.add([ 1.0f, 0.0f])
54 | texCoords.add([ 1.0f, 1.0f])
55 | texCoords.add([ 0.0f, 1.0f])
56 |
57 | #left
58 | vertCoords.add([-size.x, -size.y, size.z])
59 | vertCoords.add([-size.x, size.y, size.z])
60 | vertCoords.add([-size.x, size.y, -size.z])
61 | vertCoords.add([-size.x, -size.y, -size.z])
62 |
63 | for i in 0 .. 3:
64 | normals.add([-1.0f, 0.0f, 1.0f])
65 |
66 | texCoords.add([ 0.0f, 0.0f])
67 | texCoords.add([ 1.0f, 0.0f])
68 | texCoords.add([ 1.0f, 1.0f])
69 | texCoords.add([ 0.0f, 1.0f])
70 |
71 | #top
72 | vertCoords.add([-size.x, size.y, -size.z])
73 | vertCoords.add([-size.x, size.y, size.z])
74 | vertCoords.add([ size.x, size.y, size.z])
75 | vertCoords.add([ size.x, size.y, -size.z])
76 |
77 | for i in 0 .. 3:
78 | normals.add([0.0f, 1.0f, 0.0f])
79 |
80 | texCoords.add([ 0.0f, 0.0f])
81 | texCoords.add([ 1.0f, 0.0f])
82 | texCoords.add([ 1.0f, 1.0f])
83 | texCoords.add([ 0.0f, 1.0f])
84 |
85 | #bottom
86 | vertCoords.add([-size.x, -size.y, size.z])
87 | vertCoords.add([-size.x, -size.y, -size.z])
88 | vertCoords.add([ size.x, -size.y, -size.z])
89 | vertCoords.add([ size.x, -size.y, size.z])
90 |
91 | for i in 0 .. 3:
92 | normals.add([0.0f, -1.0f, 0.0f])
93 |
94 | texCoords.add([ 0.0f, 0.0f])
95 | texCoords.add([ 1.0f, 0.0f])
96 | texCoords.add([ 1.0f, 1.0f])
97 | texCoords.add([ 0.0f, 1.0f])
98 |
99 | proc fillIndexBuffer(indices: var seq[GLushort]) =
100 | #front
101 | indices.add([0.GLushort, 1, 2])
102 | indices.add([3.GLushort, 0, 2])
103 |
104 | #right
105 | indices.add([4.GLushort, 5, 6])
106 | indices.add([7.GLushort, 4, 6])
107 |
108 | #back
109 | indices.add([8.GLushort, 9, 10])
110 | indices.add([11.GLushort, 8, 10])
111 |
112 | #left
113 | indices.add([12.GLushort, 13, 14])
114 | indices.add([15.GLushort, 12, 14])
115 |
116 | #top
117 | indices.add([16.GLushort, 17, 18])
118 | indices.add([19.GLushort, 16, 18])
119 |
120 | #bottom
121 | indices.add([20.GLushort, 21, 22])
122 | indices.add([23.GLushort, 20, 22])
123 |
124 | method init*(c: CubeComponent) =
125 | procCall c.MeshComponent.init()
126 | c.mSize = newVector3(1.0, 1.0, 1.0)
127 |
128 | c.material.ambient = newColor(1.0, 1.0, 1.0, 0.2)
129 | c.material.diffuse = newColor(1.0, 1.0, 1.0, 1.0)
130 |
131 | proc generateMesh(c: CubeComponent) =
132 | let mesh = c
133 | var vertCoords = newSeq[float32]()
134 | var texCoords = newSeq[float32]()
135 | var normals = newSeq[float32]()
136 | var indices = newSeq[GLushort]()
137 |
138 | fillVertexBuffers(vertCoords, texCoords, normals, c.mSize)
139 | fillIndexBuffer(indices)
140 |
141 | mesh.vboData.minCoord = newVector3(high(int).Coord, high(int).Coord, high(int).Coord)
142 | mesh.vboData.maxCoord = newVector3(low(int).Coord, low(int).Coord, low(int).Coord)
143 | mesh.vboData.vertInfo = newVertexInfoWithVertexData(vertCoords.len, texCoords.len, normals.len, 0)
144 |
145 | let stride = int32( mesh.vboData.vertInfo.stride / sizeof(GLfloat) )
146 | let mSize = int32(vertCoords.len * stride / 3)
147 | var vertexData = c.createVertexData(stride, mSize, vertCoords, texCoords, normals, @[])
148 | mesh.createVBO(indices, vertexData)
149 |
150 | proc size*(cc: CubeComponent): Vector3 = cc.mSize
151 | proc `size=`*(cc: CubeComponent, v: Vector3) =
152 | cc.mSize = v
153 | cc.generateMesh()
154 |
155 | method componentNodeWasAddedToSceneView*(c: CubeComponent) =
156 | c.generateMesh()
157 |
158 | method deserialize*(c: CubeComponent, j: JsonNode, s: Serializer) =
159 | if j.isNil:
160 | return
161 |
162 | s.deserializeValue(j, "size", c.mSize)
163 |
164 | method serialize*(c: CubeComponent, s: Serializer): JsonNode =
165 | result = newJObject()
166 | result.add("size", s.getValue(c.mSize))
167 |
168 | method visitProperties*(c: CubeComponent, p: var PropertyVisitor) =
169 | p.visitProperty("size", c.size)
170 | procCall c.MeshComponent.visitProperties(p)
171 |
172 | genSerializationCodeForComponent(CubeComponent)
173 | registerComponent(CubeComponent, "Primitives")
174 |
--------------------------------------------------------------------------------
/rod/component/primitives/sphere.nim:
--------------------------------------------------------------------------------
1 | import nimx/[matrixes, types, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[vertex_data_info, component]
4 | import rod/component/[ mesh_component, material ]
5 | import rod/tools/serializer
6 | import opengl, json
7 |
8 | type SphereComponent* = ref object of MeshComponent
9 | radius: float32
10 | segments: int32
11 |
12 | SphereComponent.properties:
13 | radius
14 | segments
15 |
16 | proc fillBuffers(c: SphereComponent, vertCoords, texCoords, normals: var seq[float32], indices: var seq[GLushort]) =
17 | let segments = c.segments
18 | let segStep = 3.14 * 2.0f / float(segments * 2)
19 | var mR: Matrix4
20 | var tPos: Vector3
21 |
22 | for i in 0 .. segments * 2:
23 | vertCoords.add([ 0.0f, c.radius, 0.0f])
24 | normals.add([ 0.0f, 1.0f, 0.0f])
25 | var tx:float32 = (1.0f / (segments.float32 * 2.0)) * (segments.float32 * 2.0 - i.float32) - (0.5f / (segments.float32 * 2.0))
26 | texCoords.add([tx, 0.0f])
27 |
28 | for i in 0 .. segments * 2:
29 | vertCoords.add([ 0.0f, -c.radius, 0.0f])
30 | normals.add([ 0.0f, -1.0f, 0.0f])
31 | var tx: float32 = (1.0f / (segments.float32 * 2.0)) * (segments.float32 * 2.0 - i.float32) - (0.5f / (segments.float32 * 2.0))
32 | texCoords.add([tx, 1.0f])
33 |
34 | for i in 1 .. segments:
35 | var startVertex = int(vertCoords.len() / 3)
36 |
37 | for j in 0 .. segments * 2 + 1:
38 | mR.loadIdentity()
39 | mR.rotateY(segStep * j.float32)
40 | mR.rotateX(segStep * i.float32)
41 | var vec = newVector3(0.0, c.radius, 0.0)
42 | tPos = mR * vec
43 |
44 | vertCoords.add([tPos.x, tPos.y, tPos.z])
45 | var norm = tPos.normalized()
46 | normals.add([norm.x, norm.y, norm.z])
47 | var tx: float32 = (1.0f / (segments.float32 * 2.0)) * (segments.float32 * 2.0 - j.float32)
48 | var ty: float32 = (1.0f / segments.float32) * i.float32
49 | texCoords.add([tx, ty])
50 |
51 | if i == 1:
52 | if j != segments * 2:
53 | indices.add(GLushort(startVertex + (j + 1)))
54 | indices.add(j.GLushort)
55 | indices.add(GLushort(startVertex + (j + 0)))
56 |
57 | if i == segments - 1:
58 | if j != segments * 2:
59 | indices.add(GLushort(j + segments*2))
60 | indices.add(GLushort(startVertex + (j + 1)))
61 | indices.add(GLushort(startVertex + (j + 0)))
62 |
63 | if i != 1 and segments != 2:
64 | if j != segments * 2:
65 | indices.add(GLushort(startVertex + (j + 1)))
66 | indices.add(GLushort(startVertex - (segments * 2 + 1) + (j + 1)))
67 | indices.add(GLushort(startVertex - (segments * 2 + 1) + (j + 0)))
68 | indices.add(GLushort(startVertex + (j + 0)))
69 | indices.add(GLushort(startVertex + (j + 1)))
70 | indices.add(GLushort(startVertex - (segments * 2 + 1) + (j + 0)))
71 |
72 | method init*(c: SphereComponent) =
73 | procCall c.MeshComponent.init()
74 | c.radius = 1.0
75 | c.segments = 15
76 |
77 | proc generateMesh*(c: SphereComponent) =
78 | let mesh = c
79 |
80 | var vertCoords = newSeq[float32]()
81 | var texCoords = newSeq[float32]()
82 | var normals = newSeq[float32]()
83 | var indices = newSeq[GLushort]()
84 |
85 | c.fillBuffers(vertCoords, texCoords, normals, indices)
86 |
87 | mesh.vboData.minCoord = newVector3(high(int).Coord, high(int).Coord, high(int).Coord)
88 | mesh.vboData.maxCoord = newVector3(low(int).Coord, low(int).Coord, low(int).Coord)
89 | mesh.vboData.vertInfo = newVertexInfoWithVertexData(vertCoords.len, texCoords.len, normals.len, 0)
90 |
91 | let stride = int32( mesh.vboData.vertInfo.stride / sizeof(GLfloat) )
92 | let size = int32(vertCoords.len * stride / 3)
93 | var vertexData = c.createVertexData(stride, size, vertCoords, texCoords, normals, @[])
94 |
95 | mesh.createVBO(indices, vertexData)
96 |
97 | mesh.material.ambient = newColor(1.0, 1.0, 1.0, 0.2)
98 | mesh.material.diffuse = newColor(1.0, 1.0, 1.0, 1.0)
99 |
100 | method componentNodeWasAddedToSceneView*(c: SphereComponent) =
101 | c.generateMesh()
102 |
103 | method deserialize*(c: SphereComponent, j: JsonNode, s: Serializer) =
104 | if j.isNil:
105 | return
106 |
107 | s.deserializeValue(j, "radius", c.radius)
108 | s.deserializeValue(j, "segments", c.segments)
109 |
110 | method serialize*(c: SphereComponent, s: Serializer): JsonNode =
111 | result = newJObject()
112 | result.add("radius", s.getValue(c.radius))
113 | result.add("segments", s.getValue(c.segments))
114 |
115 | method visitProperties*(c: SphereComponent, p: var PropertyVisitor) =
116 | template radiusAux(cc: SphereComponent): float = c.radius
117 | template `radiusAux=`(cc: SphereComponent, v: float) =
118 | cc.radius = v
119 | cc.generateMesh()
120 |
121 | template segmentsAux(cc: SphereComponent): int32 = c.segments
122 | template `segmentsAux=`(cc: SphereComponent, v: int32) =
123 | cc.segments = v
124 | cc.generateMesh()
125 |
126 | p.visitProperty("radius", c.radiusAux)
127 | p.visitProperty("segments", c.segmentsAux)
128 | procCall c.MeshComponent.visitProperties(p)
129 |
130 | genSerializationCodeForComponent(SphereComponent)
131 | registerComponent(SphereComponent, "Primitives")
132 |
--------------------------------------------------------------------------------
/rod/component/solid.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, matrixes, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[rod_types, component]
4 | import rod/tools/serializer
5 | import json
6 |
7 | type Solid* = ref object of RenderComponent
8 | size*: Size
9 | color*: Color
10 |
11 | Solid.properties:
12 | size
13 | color
14 |
15 | method init*(s: Solid) =
16 | s.color = whiteColor()
17 | s.size = newSize(10, 10)
18 |
19 | method deserialize*(s: Solid, j: JsonNode, serializer: Serializer) =
20 | var v = j{"color"}
21 | if not v.isNil:
22 | s.color = newColor(v[0].getFloat(), v[1].getFloat(), v[2].getFloat())
23 | v = j{"alpha"} # Deprecated.
24 | if not v.isNil:
25 | s.node.alpha = v.getFloat(1.0)
26 |
27 | v = j{"size"}
28 | if not v.isNil:
29 | s.size = newSize(v[0].getFloat(), v[1].getFloat())
30 |
31 | genSerializationCodeForComponent(Solid)
32 |
33 | method draw*(s: Solid) =
34 | let c = currentContext()
35 | var r: Rect
36 | r.size = s.size
37 | c.fillColor = s.color
38 | c.strokeWidth = 0
39 | c.drawRect(r)
40 |
41 | method getBBox*(s: Solid): BBox =
42 | result.minPoint = newVector3(0.0, 0.0, 0.0)
43 | result.maxPoint = newVector3(s.size.width, s.size.height, 0.0)
44 |
45 | method visitProperties*(c: Solid, p: var PropertyVisitor) =
46 | p.visitProperty("size", c.size)
47 | p.visitProperty("color", c.color)
48 |
49 | method serialize*(c: Solid, s: Serializer): JsonNode =
50 | result = newJObject()
51 | result.add("size", s.getValue(c.size))
52 | result.add("color", s.getValue(c.color))
53 |
54 | registerComponent(Solid)
55 |
--------------------------------------------------------------------------------
/rod/component/sprite.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, context, image, animation, property_visitor ]
2 | import rod / [rod_types, node, component, tools/serializer]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 |
5 | import json, logging
6 |
7 | type Sprite* = ref object of RenderComponent
8 | offset*: Point
9 | frameOffsets*: seq[Point]
10 | images*: seq[Image]
11 | mCurrentFrame*: int16
12 |
13 | Sprite.properties:
14 | images:
15 | serializationKey: "fileNames"
16 | combinedWith: frameOffsets
17 | mCurrentFrame:
18 | serializationKey: "currentFrame"
19 | offset
20 |
21 | template `currentFrame`*(s: Sprite): int =
22 | int(s.mCurrentFrame)
23 |
24 | template `currentFrame=`*(s: Sprite, v: int) =
25 | s.mCurrentFrame = int16(v)
26 |
27 | proc image*(s: Sprite): Image =
28 | if s.images.len > s.currentFrame and s.currentFrame >= 0:
29 | result = s.images[s.currentFrame]
30 |
31 | proc `image=`*(s: Sprite, i: Image) =
32 | s.images.setLen(1)
33 | s.images[0] = i
34 | s.currentFrame = 0
35 |
36 | proc getOffset*(s: Sprite): Point =
37 | result = s.offset
38 | if s.frameOffsets.len > s.currentFrame and s.currentFrame >= 0:
39 | result += s.frameOffsets[s.currentFrame]
40 |
41 | proc calculatedSize(s: Sprite): Size =
42 | ## If size is zeroSize - return image size.
43 | if not s.image.isNil:
44 | result = s.image.size
45 |
46 | proc effectiveSize*(s: Sprite): Size =
47 | result = s.calculatedSize()
48 |
49 | method draw*(s: Sprite) =
50 | let c = currentContext()
51 |
52 | let i = s.image
53 | if not i.isNil:
54 | var r: Rect
55 | r.origin = s.getOffset()
56 | r.size = s.calculatedSize()
57 | c.drawImage(i, r)
58 |
59 | proc createFrameAnimation(s: Sprite) {.inline.} =
60 | let a = newAnimation()
61 | const fps = 1.0 / 30.0
62 | a.loopDuration = float(s.images.len) * fps
63 | a.continueUntilEndOfLoopOnCancel = true
64 | a.onAnimate = proc(p: float) =
65 | s.currentFrame = int16(float(s.images.len - 1) * p)
66 | s.node.registerAnimation("sprite", a)
67 |
68 | method getBBox*(s: Sprite): BBox =
69 | let sz = s.effectiveSize()
70 | result.maxPoint = newVector3(sz.width + s.getOffset.x, sz.height + s.getOffset.y, 0.0)
71 | result.minPoint = newVector3(s.getOffset.x, s.getOffset.y, 0.0)
72 |
73 | proc awake(c: Sprite) =
74 | if c.images.len > 1:
75 | c.createFrameAnimation()
76 |
77 | genSerializationCodeForComponent(Sprite)
78 |
79 | template curFrameAux(t: Sprite): int16 = t.mCurrentFrame
80 | template `curFrameAux=`(t: Sprite, f: int16) = t.mCurrentFrame = f
81 |
82 | method visitProperties*(t: Sprite, p: var PropertyVisitor) =
83 | p.visitProperty("image", t.image)
84 | p.visitProperty("curFrame", t.curFrameAux)
85 | p.visitProperty("offset", t.offset)
86 |
87 | registerComponent(Sprite)
88 |
--------------------------------------------------------------------------------
/rod/component/tint.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, composition, portable_gl, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod/[component, tools/serializer]
4 | import json
5 |
6 |
7 | type Tint* = ref object of RenderComponent
8 | black*: Color
9 | white*: Color
10 | amount*: float32
11 |
12 | Tint.properties:
13 | black
14 | white
15 | amount
16 |
17 | var effect = newPostEffect("""
18 | void tint_effect(vec4 black, vec4 white, float amount) {
19 | float b = (0.2126*gl_FragColor.r + 0.7152*gl_FragColor.g + 0.0722*gl_FragColor.b); // Maybe the koeffs should be adjusted
20 | float a = gl_FragColor.a;
21 | vec4 res = mix(black, white, b);
22 | res.a *= a;
23 | gl_FragColor = mix(gl_FragColor, res, amount);
24 | }
25 | """, "tint_effect", ["vec4", "vec4", "float"])
26 |
27 | method deserialize*(c: Tint, j: JsonNode, s: Serializer) =
28 | var v = j["black"]
29 | c.black = newColor(v[0].getFloat(), v[1].getFloat(), v[2].getFloat(), v[3].getFloat())
30 | v = j["white"]
31 | c.white = newColor(v[0].getFloat(), v[1].getFloat(), v[2].getFloat(), v[3].getFloat())
32 | c.amount = j{"amount"}.getFloat(1)
33 |
34 | method beforeDraw*(c: Tint, index: int): bool =
35 | pushPostEffect(effect, c.black, c.white, c.amount)
36 |
37 | method afterDraw*(c: Tint, index: int) =
38 | popPostEffect()
39 |
40 | method visitProperties*(c: Tint, p: var PropertyVisitor) =
41 | p.visitProperty("black", c.black)
42 | p.visitProperty("white", c.white)
43 | p.visitProperty("amount", c.amount)
44 |
45 | genSerializationCodeForComponent(Tint)
46 | registerComponent(Tint, "Effects")
47 |
--------------------------------------------------------------------------------
/rod/component/ui_component.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ view, matrixes, view_event_handling, property_visitor ]
2 | import rod / [ component, ray, viewport, node, rod_types ]
3 | import logging
4 | export UIComponent
5 |
6 | type UICompView = ref object of View
7 | uiComp: UIComponent
8 | uiCompSubview: View
9 |
10 | method `enabled=`*(c: UIComponent, state: bool) {.base.}=
11 | c.mEnabled = state
12 |
13 | proc enabled*(c: UIComponent): bool =
14 | result = c.mEnabled
15 |
16 | proc view*(c: UIComponent): View =
17 | if not c.mView.isNil:
18 | result = c.mView.UICompView.uiCompSubview
19 |
20 | proc intersectsWithUIPlane(uiComp: UIComponent, r: Ray, res: var Vector3): bool=
21 | let n = uiComp.node
22 | let worldPointOnPlane = n.localToWorld(newVector3())
23 | var worldNormal = n.localToWorld(newVector3(0, 0, 1))
24 | worldNormal -= worldPointOnPlane
25 | worldNormal.normalize()
26 | result = r.intersectWithPlane(worldNormal, worldPointOnPlane, res)
27 |
28 | proc intersectsWithUINode*(uiComp: UIComponent, r: Ray, res: var Vector3): bool =
29 | if uiComp.intersectsWithUIPlane(r, res) and not uiComp.mView.isNil:
30 | let v = uiComp.view
31 | if not v.isNil:
32 | var localres : Vector3
33 | if uiComp.node.tryWorldToLocal(res, localres):
34 | result = localres.x >= v.frame.x and localres.x <= v.frame.maxX and
35 | localres.y >= v.frame.y and localres.y <= v.frame.maxY
36 |
37 | method convertPointToParent*(v: UICompView, p: Point): Point =
38 | result = newPoint(-9999999, -9999999) # Some ridiculous value
39 | warn "UICompView.convertPointToParent not implemented"
40 |
41 | method convertPointFromParent*(v: UICompView, p: Point): Point =
42 | result = newPoint(-9999999, -9999999) # Some ridiculous value
43 | if not v.uiComp.node.sceneView.isNil:
44 | let r = v.uiComp.node.sceneView.rayWithScreenCoords(p)
45 | var res : Vector3
46 | if v.uiComp.intersectsWithUIPlane(r, res):
47 | if v.uiComp.node.tryWorldToLocal(res, res):
48 | result = newPoint(res.x, res.y)
49 |
50 | method draw*(c: UIComponent) =
51 | if not c.mView.isNil:
52 | c.mView.recursiveDrawSubviews()
53 |
54 | proc updSuperview(c: UIComponent) =
55 | if not c.mView.isNil and not c.node.sceneView.isNil:
56 | c.mView.superview = c.node.sceneView
57 | c.mView.window = c.node.sceneView.window
58 | c.mView.addSubview(c.mView.UICompView.uiCompSubview)
59 |
60 | proc `view=`*(c: UIComponent, v: View) =
61 | if not c.view.isNil:
62 | c.view.removeFromSuperview()
63 |
64 | if v == nil:
65 | c.mView = nil
66 | return
67 |
68 | let cv = UICompView.new(newRect(0, 0, 20, 20))
69 | cv.uiComp = c
70 | c.mView = cv
71 | c.enabled = true
72 | cv.uiCompSubview = v
73 | v.name = c.node.name
74 | c.updSuperview()
75 |
76 | proc moveToWindow(v: View, w: Window) =
77 | v.window = w
78 | for s in v.subviews:
79 | s.moveToWindow(w)
80 |
81 | proc handleScrollEv*(c: UIComponent, r: Ray, e: var Event, intersection: Vector3): bool =
82 | var res : Vector3
83 | if c.node.tryWorldToLocal(intersection, res):
84 | let v = c.view
85 | let tmpLocalPosition = e.localPosition
86 | e.localPosition = v.convertPointFromParent(newPoint(res.x, res.y))
87 | if e.localPosition.inRect(v.bounds):
88 | result = v.processMouseWheelEvent(e)
89 |
90 | e.localPosition = tmpLocalPosition
91 |
92 | proc handleTouchEv*(c: UIComponent, r: Ray, e: var Event, intersection: Vector3): bool =
93 | var res : Vector3
94 | if c.node.tryWorldToLocal(intersection, res):
95 | let v = c.view
96 | let tmpLocalPosition = e.localPosition
97 | e.localPosition = v.convertPointFromParent(newPoint(res.x, res.y))
98 | if e.localPosition.inRect(v.bounds):
99 | result = v.processTouchEvent(e)
100 | if result and e.buttonState == bsDown:
101 | c.mView.touchTarget = v
102 |
103 | e.localPosition = tmpLocalPosition
104 |
105 | proc sceneViewWillMoveToWindow*(c: UIComponent, w: Window) =
106 | if not c.mView.isNil:
107 | c.mView.viewWillMoveToWindow(w)
108 | c.mView.moveToWindow(w)
109 |
110 | method componentNodeWasAddedToSceneView*(ui: UIComponent) =
111 | let sv = ui.node.sceneView
112 | sv.uiComponents.add(ui)
113 |
114 | ui.updSuperview()
115 |
116 | method componentNodeWillBeRemovedFromSceneView(ui: UIComponent) =
117 | let sv = ui.node.sceneView
118 | let i = sv.uiComponents.find(ui)
119 | if i != -1:
120 | sv.uiComponents.del(i)
121 |
122 | if not ui.view.isNil:
123 | ui.view.removeFromSuperview()
124 |
125 | method visitProperties*(ui: UIComponent, p: var PropertyVisitor) =
126 | p.visitProperty("enabled", ui.enabled)
127 | if not ui.view.isNil:
128 | ui.view.visitProperties(p)
129 |
130 | registerComponent(UIComponent)
131 |
--------------------------------------------------------------------------------
/rod/component/vector_shape.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, matrixes, property_visitor]
2 | import rod / utils / [ property_desc, serialization_codegen ]
3 | import rod / [ rod_types, component, tools/serializer]
4 | import json
5 |
6 | type VectorShapeType* = enum
7 | vsRectangle
8 | vsEllipse
9 | vsStar
10 |
11 | type VectorShape* = ref object of RenderComponent
12 | color*: Color
13 | strokeColor*: Color
14 | size*: Size
15 | strokeWidth*: float32
16 | radius*: float32
17 | shapeType*: VectorShapeType
18 |
19 | VectorShape.properties:
20 | size
21 | color
22 | strokeWidth
23 | strokeColor
24 | radius
25 | shapeType
26 |
27 | genSerializationCodeForComponent(VectorShape)
28 |
29 | method init*(vs: VectorShape) =
30 | vs.color = whiteColor()
31 | vs.size = newSize(100, 100)
32 | vs.strokeWidth = 0
33 | vs.strokeColor = whiteColor()
34 | vs.radius = 0
35 |
36 | method serialize*(vs: VectorShape, s: Serializer): JsonNode =
37 | result = newJObject()
38 | result.add("size", s.getValue(vs.size))
39 | result.add("color", s.getValue(vs.color))
40 | result.add("strokeWidth", s.getValue(vs.strokeWidth))
41 | result.add("strokeColor", s.getValue(vs.strokeColor))
42 | result.add("radius", s.getValue(vs.radius))
43 | result.add("shapeType", s.getValue(vs.shapeType))
44 |
45 | method deserialize*(vs: VectorShape, j: JsonNode, serializer: Serializer) =
46 | serializer.deserializeValue(j, "color", vs.color)
47 | serializer.deserializeValue(j, "size", vs.size)
48 | serializer.deserializeValue(j, "strokeWidth", vs.strokeWidth)
49 | serializer.deserializeValue(j, "strokeColor", vs.strokeColor)
50 | serializer.deserializeValue(j, "radius", vs.radius)
51 | serializer.deserializeValue(j, "shapeType", vs.shapeType)
52 |
53 | method getBBox*(vs: VectorShape): BBox =
54 | result.minPoint = newVector3(-vs.size.width/2.0, -vs.size.height/2.0, 0.0)
55 | result.maxPoint = newVector3(vs.size.width/2.0, vs.size.height/2.0, 0.0)
56 |
57 | method visitProperties*(vs: VectorShape, p: var PropertyVisitor) =
58 | p.visitProperty("size", vs.size)
59 | p.visitProperty("color", vs.color)
60 | p.visitProperty("strokeWidth", vs.strokeWidth)
61 | p.visitProperty("strokeColor", vs.strokeColor)
62 | p.visitProperty("radius", vs.radius)
63 | p.visitProperty("shapeType", vs.shapeType)
64 |
65 | method beforeDraw*(vs: VectorShape, index: int): bool =
66 | let c = currentContext()
67 | c.fillColor = vs.color
68 | c.strokeWidth = vs.strokeWidth
69 | c.strokeColor = vs.strokeColor
70 | let strokedWidth = vs.size.width + vs.strokeWidth
71 | let strokedHeight = vs.size.height + vs.strokeWidth
72 | let r = newRect(-strokedWidth/2.0, -strokedHeight/2.0, strokedWidth, strokedHeight)
73 |
74 | case vs.shapeType:
75 | of vsRectangle:
76 | if vs.radius > 0:
77 | c.drawRoundedRect(r, vs.radius + vs.strokeWidth/2.0)
78 | else:
79 | c.drawRect(r)
80 | of vsEllipse:
81 | c.drawEllipseInRect(r)
82 | else: discard
83 |
84 | registerComponent(VectorShape)
85 |
--------------------------------------------------------------------------------
/rod/component/visual_modifier.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, context, portable_gl]
2 | import rod/[component, tools/serializer]
3 | import rod / utils / [ property_desc, serialization_codegen ]
4 | import opengl, json
5 |
6 | type BlendMode * = enum
7 | COLOR_ADD = GL_ONE
8 | COLOR_SCREEN = GL_ONE_MINUS_SRC_COLOR
9 | COLOR_MULTIPLY = GL_ONE_MINUS_SRC_ALPHA
10 |
11 | type VisualModifier* = ref object of RenderComponent
12 | blendMode*: BlendMode
13 |
14 | VisualModifier.properties:
15 | discard
16 |
17 | method init*(vm: VisualModifier) =
18 | procCall vm.Component.init()
19 | vm.blendMode = COLOR_ADD
20 |
21 | method beforeDraw*(vm: VisualModifier, index: int): bool =
22 | let gl = currentContext().gl
23 |
24 | if vm.blendMode == COLOR_SCREEN:
25 | gl.blendFunc(GL_ONE, vm.blendMode.GLenum)
26 | else:
27 | gl.blendFunc(gl.SRC_ALPHA, vm.blendMode.GLenum)
28 |
29 | method afterDraw*(vm: VisualModifier, index: int) =
30 | let gl = currentContext().gl
31 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
32 |
33 | method deserialize*(vm: VisualModifier, j: JsonNode, serealizer: Serializer) =
34 | var v = j{"blendMode"}
35 | if not v.isNil:
36 | let bm = v.getStr()
37 | case bm
38 | of "ADD": vm.blendMode = COLOR_ADD
39 | else: discard
40 |
41 | method serialize*(vm: VisualModifier, s: Serializer): JsonNode=
42 | result = newJObject()
43 | result.add("blendMode", %($vm.blendMode).substr(5))
44 |
45 | genSerializationCodeForComponent(VisualModifier)
46 | registerComponent(VisualModifier, "Effects")
47 |
--------------------------------------------------------------------------------
/rod/editor/editor_assets_view.nim:
--------------------------------------------------------------------------------
1 | import nimx / [types, matrixes, view, table_view_cell, text_field,
2 | scroll_view, button, event, linear_layout, collection_view, formatted_text, image,
3 | context, drag_and_drop, pasteboard / pasteboard_item, view_render_to_image,
4 | segmented_control ]
5 |
6 | import file_system / [ filesystem_view ]
7 | import variant, tables
8 | import rod / [edit_view]
9 | import times, os, strutils
10 |
11 | type EditorAssetsView* = ref object of EditorTabView
12 | fileSystemView: FileSystemView
13 |
14 | method init*(v: EditorAssetsView, r: Rect)=
15 | procCall v.View.init(r)
16 |
17 | v.fileSystemView = createProjectFSView(v.editor.currentProjectPath(), newRect(0.0, 0.0, v.bounds.width, v.bounds.height - 20.0))
18 | v.fileSystemView.resizingMask="wh"
19 | v.addSubview(v.fileSystemView)
20 |
21 | var bottomMenu = newView(newRect(0, v.bounds.height - 20.0, v.bounds.width, 20))
22 | bottomMenu.autoresizingMask = { afFlexibleWidth, afFlexibleMinY }
23 | v.addSubview(bottomMenu)
24 |
25 | var segm = SegmentedControl.new(newRect(bottomMenu.bounds.width - 150.0, 0.0, 150, 20.0))
26 | segm.segments = @["compact", "normal"]
27 | segm.autoresizingMask = { afFlexibleMinX, afFlexibleHeight }
28 | segm.selectedSegment = 1
29 | bottomMenu.addSubview(segm)
30 | segm.onAction do():
31 | v.fileSystemView.cachedResources.clear()
32 | v.fileSystemView.contentView.setCompact segm.selectedSegment == 0
33 |
34 | var curPath = newLabel(newRect(0,0,bottomMenu.bounds.width-segm.bounds.width, 20.0))
35 | bottomMenu.addSubview(curPath)
36 | v.fileSystemView.onPathChanged do(np: string):
37 | curPath.text = np
38 |
39 | var filter = newTextField(newRect(segm.frame.origin.x - 250.0, 0, 220.0, 20.0))
40 | filter.autoresizingMask = { afFlexibleMinX, afFlexibleHeight }
41 | filter.continuous = true
42 | filter.onAction do():
43 | v.fileSystemView.onFilterContent() do(path: string) -> bool:
44 | let sp = path.splitFile()
45 | result = sp.name == filter.text or filter.text in sp.name
46 | if not result:
47 | result = sp.ext == filter.text or filter.text in sp.ext
48 |
49 | bottomMenu.addSubview(filter)
50 |
51 | v.fileSystemView.onDragStart do(fileViews: seq[FilePreview]):
52 | var drag_data = ""
53 | var drag_kind = ""
54 |
55 | for fileView in fileViews:
56 | var pbk = ""
57 |
58 | if fileView.kind == akComposition:
59 | pbk = rodPbComposition
60 | elif fileView.kind == akImage:
61 | pbk = rodPbSprite
62 |
63 | if pbk.len > 0:
64 | if drag_kind.len > 0:
65 | drag_kind = rodPbFiles
66 | drag_data &= ":" & fileView.path
67 | else:
68 | drag_kind = pbk
69 | drag_data = fileView.path
70 |
71 | if drag_data.len > 0 and drag_kind.len > 0:
72 | var dpi = newPasteboardItem(drag_kind, drag_data)
73 | var img: Image = nil
74 | img = fileViews[0].screenShot()
75 | startDrag(dpi, img)
76 |
77 | v.fileSystemView.onDoubleClicked do(item: FilePreview):
78 | if item.kind == akComposition:
79 | v.editor.openComposition(item.path)
80 |
81 | method tabSize*(v: EditorAssetsView, bounds: Rect): Size=
82 | result = newSize(bounds.width, 450.0)
83 |
84 | method tabAnchor*(v: EditorAssetsView): EditorTabAnchor =
85 | result = etaBottom
86 |
87 | method update*(v: EditorAssetsView)=
88 | v.fileSystemView.reloadIfNeeded()
89 |
90 | registerEditorTab("Assets", EditorAssetsView)
91 |
92 | #[
93 | HELPERS
94 | ]#
95 |
96 |
--------------------------------------------------------------------------------
/rod/editor/editor_console.nim:
--------------------------------------------------------------------------------
1 | import nimx/[view, text_field, button, popup_button, scroll_view]
2 | import strutils, tables, logging
3 | import rod/edit_view
4 | import rod/editor/editor_error_handling
5 | import variant
6 |
7 | export view
8 |
9 |
10 | type EditorConsole* = ref object of EditorTabView
11 | contentView: View
12 |
13 | scView: ScrollView
14 | currentLevel: Level
15 |
16 | proc reloadConsole(v: EditorConsole) =
17 | var msgs = gEditorLogger.dump(v.currentLevel)
18 |
19 | while v.contentView.subviews.len > 0:
20 | v.contentView.subviews[0].removeFromSuperView()
21 |
22 | var lblText = "\n"
23 | for msg in msgs:
24 | lblText &= msg & "\n"
25 |
26 | var lines = max(lblText.count("\n"),1)
27 | # info "lines ", lines
28 |
29 | var lbl = newLabel(newRect(10, 10, v.contentView.bounds.width, lines.float * 20.0))
30 | lbl.resizingMask = "wh"
31 | lbl.backgroundColor = whiteColor()
32 | lbl.text = lblText
33 | # lbl.setBoundsSize(newSize(v.contentView.bounds.width, lines.float * 20.0))
34 | v.contentView.setFrameSize(newSize(v.contentView.bounds.width, lines.float * 20.0 + 10.0))
35 | v.contentView.addSubview(lbl)
36 |
37 |
38 | method init*(v: EditorConsole, r: Rect) =
39 | procCall v.View.init(r)
40 | v.resizingMask = "wh"
41 |
42 | v.contentView = newView(newRect(0, 0, v.bounds.width, v.bounds.height))
43 | v.contentView.resizingMask = "wh"
44 |
45 | v.scView = newScrollView(v.contentView)
46 | v.scView.horizontalScrollBar = nil
47 | v.scView.resizingMask = "wh"
48 | # v.scView.setFrame(newRect(0.0, 20.0, v.bounds.width, v.bounds.height - 20.0))
49 | v.addSubview(v.scView)
50 |
51 | v.currentLevel = 0.Level
52 | var popupButton = PopupButton.new(newRect(v.bounds.width - 240.0, 0, 240, 20))
53 | popupButton.autoresizingMask = {afFlexibleMinX, afFlexibleMaxY}
54 |
55 | var items = newSeq[string]()
56 | for lvl in low(Level) .. high(Level):
57 | items.add($lvl)
58 | popupButton.items = items
59 | popupButton.onAction do():
60 | v.currentLevel = popupButton.selectedIndex.Level
61 | v.reloadConsole()
62 | v.addSubview(popupButton)
63 |
64 | v.reloadConsole()
65 |
66 | method tabSize*(v: EditorConsole, bounds: Rect): Size=
67 | result = newSize(bounds.width, 250.0)
68 |
69 | method tabAnchor*(v: EditorConsole): EditorTabAnchor =
70 | result = etaBottom
71 |
72 | import times
73 | const updateRate = 0.25
74 | var lastUpdate = 0.0
75 |
76 | method update*(v: EditorConsole)=
77 | let t = epochTime()
78 | if t - lastUpdate > updateRate:
79 | lastUpdate = t
80 | v.reloadConsole()
81 |
82 | registerEditorTab("Console", EditorConsole)
83 |
--------------------------------------------------------------------------------
/rod/editor/editor_default_tabs.nim:
--------------------------------------------------------------------------------
1 | import rod/editor/[editor_inspector_view, editor_tree_view,
2 | animation/editor_animation_view, scene/editor_scene_view, scene/editor_scene_settings,
3 | editor_console
4 | ]
5 |
6 | when not (defined(js) or defined(emscripten) or defined(android) or defined(ios)):
7 | import rod/editor/editor_assets_view
8 |
--------------------------------------------------------------------------------
/rod/editor/editor_error_handling.nim:
--------------------------------------------------------------------------------
1 | import logging, tables, strutils, times
2 |
3 | type EditorLogger* = ref object of Logger
4 | msgDump*: TableRef[Level, seq[string]]
5 |
6 | method log*(logger: EditorLogger, level: Level, args: varargs[string,`$`])=
7 | var t = "[" & getClockStr() & "] "
8 | var msg = ""
9 | for arg in args:
10 | if arg.len == 0:
11 | msg &= "nil"
12 | else:
13 | msg &= arg
14 |
15 | var msgseq = msg.split("\n")
16 |
17 | if msgseq.len > 1:
18 | msg = ""
19 | for i, m in msgseq:
20 | msg &= (if i < msgseq.len - 1: "\n" else: "") & t & m
21 | else:
22 | msg = t & msg
23 |
24 | if logger.msgDump.isNil:
25 | logger.msgDump = newTable[Level, seq[string]]()
26 |
27 | var dump = logger.msgDump.getOrDefault(level)
28 | dump.add(msg)
29 | logger.msgDump[level] = dump
30 |
31 | proc clear*(logger: EditorLogger, level: Level)=
32 | var dump = logger.msgDump.getOrDefault(level)
33 | dump.setLen(0)
34 | logger.msgDump[level] = dump
35 |
36 | proc clearAll*(logger: EditorLogger)=
37 | for level in low(Level) .. high(Level):
38 | logger.clear(level)
39 |
40 | proc dump*(logger: EditorLogger, level: Level):seq[string]=
41 | result = @[]
42 | if logger.msgDump.isNil: return
43 | if level == lvlAll:
44 | for k, v in logger.msgDump:
45 | result.add(v)
46 | else:
47 | var dump = logger.msgDump.getOrDefault(level)
48 | result.add(dump)
49 |
50 | var gEditorLogger* {.threadvar.}: EditorLogger
51 | gEditorLogger.new()
52 |
53 | addHandler(gEditorLogger)
54 |
--------------------------------------------------------------------------------
/rod/editor/editor_project_settings.nim:
--------------------------------------------------------------------------------
1 | import nimx/[matrixes, types]
2 | import rod / rod_types
3 | import json, os, logging, options
4 |
5 | type
6 | EditorTabSettings* = object
7 | name*: string
8 | frame*: Rect
9 |
10 | EditorSettings* = object
11 | AutosaveInterval: Option[float]
12 | EditorCameras: Option[seq[EditorCameraSettings]]
13 |
14 | EditorCameraSettings* = object
15 | name*: string
16 | viewportSize*: Size
17 | projectionMode*: CameraProjection
18 |
19 | EditorProject* = object
20 | name*: string
21 | path*: string
22 | tabs*: seq[EditorTabSettings]
23 | composition*: string
24 | settings*: EditorSettings
25 |
26 | const SettingsFileName = "settings.json"
27 |
28 | let UserSettingsPath = getHomeDir() & "/.rodedit/" & SettingsFileName
29 | let ProjSettingsPath = getAppDir() & "/.rodedit/" & SettingsFileName
30 |
31 | template settingsFiles(): seq[string] =
32 | @[UserSettingsPath, ProjSettingsPath]
33 |
34 | proc merge(a, b: JsonNode): JsonNode =
35 | if a.isNil and b.isNil: return
36 | if a.isNil:
37 | result = parseJson($b)
38 | else:
39 | if b.kind == a.kind and b.kind == JObject:
40 | result = parseJson($a)
41 | for k, v in b:
42 | if k notin a:
43 | result[k] = v
44 | else:
45 | result[k] = merge(a[k], b[k])
46 | else:
47 | result = parseJson($b)
48 |
49 | proc getEditorSettingsJson(): JsonNode =
50 | var jsettings: JsonNode
51 | try:
52 | for sf in settingsFiles:
53 | info "getEditorSettingsJson: ", sf
54 | if fileExists(sf):
55 | jsettings = merge(jsettings, parseFile(sf))
56 | except Exception as e:
57 | warn "Can't parse editor settings! ", e.msg, "\n", e.getStackTrace()
58 | result = jsettings
59 |
60 | proc loadEditorSettings*(proj: var EditorProject) =
61 | let jsettings = getEditorSettingsJson()
62 | if not jsettings.isNil:
63 | proj.settings = jsettings.to(EditorSettings)
64 | echo "loadEditorSettings ", if jsettings.isNil: "nil" else: $jsettings
65 |
66 | proc autosaveInterval*(p: EditorProject): float =
67 | result = 120.0
68 | if p.settings.AutosaveInterval.isSome:
69 | result = p.settings.AutosaveInterval.get()
70 |
71 | proc editorCameras*(p: EditorProject): seq[EditorCameraSettings] =
72 | result = @[
73 | EditorCameraSettings(name: "[EditorCamera2D]", viewportSize: newSize(1920, 1080), projectionMode: cpOrtho),
74 | EditorCameraSettings(name: "[EditorCamera3D]", viewportSize: newSize(1920, 1080), projectionMode: cpPerspective)
75 | ]
76 | if not p.settings.EditorCameras.isSome: return
77 | var r = p.settings.EditorCameras.get()
78 | if r.len == 0: return
79 | result = r
80 |
--------------------------------------------------------------------------------
/rod/editor/editor_tab_registry.nim:
--------------------------------------------------------------------------------
1 | import typetraits
2 | import nimx / [ types, view ]
3 |
4 | const RodInternalTab* = "RodInternalTab"
5 |
6 | type
7 | EditViewEntry* = tuple
8 | name: string
9 | create: proc(): View {.gcsafe.}
10 |
11 | var gRegisteredViews {.threadvar.}: seq[EditViewEntry]
12 | gRegisteredViews = newSeq[EditViewEntry]()
13 |
14 | proc registerEditorTabAux(tn: string, t: typedesc) =
15 | var evr: EditViewEntry
16 | evr.name = tn
17 | let typename = typetraits.name(t)
18 | evr.create = proc(): View {.gcsafe.} =
19 | result = newObjectOfClass(typename).View
20 | result.name = evr.name
21 |
22 | gRegisteredViews.add(evr)
23 |
24 | template registerEditorTab*(tn: string, t: typedesc) =
25 | doAssert(tn.len > 0, "Tab must have the name")
26 | registerClass(t)
27 | registerEditorTabAux(tn, t)
28 |
29 | iterator registeredEditorTabs*():EditViewEntry=
30 | for rt in gRegisteredViews:
31 | yield rt
32 |
33 | proc getRegisteredEditorTab*(name: string): EditViewEntry=
34 | for rt in registeredEditorTabs():
35 | echo "find ", name, " != ", rt.name
36 | if rt.name == name:
37 | echo "find tab ", name
38 | return rt
39 |
--------------------------------------------------------------------------------
/rod/editor/editor_types.nim:
--------------------------------------------------------------------------------
1 | import nimx / [view, button, editor/tab_view, linear_layout, popup_button,
2 | toolbar, notification_center, event, animation ]
3 | import rod / [node, viewport, editor/editor_project_settings]
4 | import rod / editor / animation / animation_editor_types
5 |
6 | export notification_center, editor_project_settings
7 |
8 | const toolbarHeight* = 30
9 |
10 | const loadingAndSavingAvailable* = not defined(android) and not defined(ios) and
11 | not defined(emscripten) and not defined(js)
12 |
13 | type
14 | EditMode* = enum
15 | emScene
16 | emAnimation
17 |
18 | EditorTabAnchor* = enum
19 | etaLeft
20 | etaRight
21 | etaBottom
22 | etaCenter
23 |
24 | EditorTabView* = ref object of View
25 | rootNode*: Node
26 | editor*: Editor
27 | composition*: CompositionDocument
28 |
29 | CompositionDocument* = ref object
30 | path*: string
31 | rootNode*: Node
32 | selectedNode*: Node
33 | owner*: EditorTabView
34 |
35 | animations*: seq[EditedAnimation]
36 | currentAnimation*: EditedAnimation
37 |
38 | Editor* = ref object
39 | mode*: EditMode
40 | sceneInput*: bool
41 | currentProject*: EditorProject
42 | mCurrentComposition*: CompositionDocument
43 | rootNode*: Node
44 | sceneView*: SceneView
45 | window*: Window
46 | mSelectedNode*: Node
47 | startFromGame*: bool
48 | workspaceView*: WorkspaceView
49 | cameraSelector*: PopupButton
50 | notifCenter*: NotificationCenter
51 |
52 | WorkspaceView* = ref object of View
53 | editor*: Editor
54 | toolbar*: Toolbar
55 | tabs*: seq[EditorTabView]
56 | tabViews*: seq[TabView]
57 | compositionEditors*: seq[EditorTabView]
58 | anchors*: array[4, TabView]
59 | horizontalLayout*: LinearLayout
60 | verticalLayout*: LinearLayout
61 | onKeyDown*: proc(e: var Event): bool {.gcsafe.}
62 |
63 | template selectedNode*(e: Editor): Node = e.mSelectedNode
64 |
65 | method setEditedNode*(v: EditorTabView, n: Node) {.base.}=
66 | discard
67 |
68 | method update*(v: EditorTabView) {.base.}= discard
69 |
70 | method tabSize*(v: EditorTabView, bounds: Rect): Size {.base.}=
71 | result = bounds.size
72 |
73 | method tabAnchor*(v: EditorTabView): EditorTabAnchor {.base.}=
74 | result = etaCenter
75 |
76 | method onEditorTouchDown*(v: EditorTabView, e: var Event) {.base.}=
77 | discard
78 |
79 | method onSceneChanged*(v: EditorTabView) {.base, deprecated.}=
80 | discard
81 |
82 | method onCompositionChanged*(v: EditorTabView, comp: CompositionDocument) {.base.} =
83 | v.composition = comp
84 |
85 | method onCompositionSaved*(v: EditorTabView, comp: CompositionDocument) {.base.} = discard
86 |
87 | method onEditModeChanged*(v: EditorTabView, mode: EditMode) {.base.} = discard
88 |
89 | # Notifications
90 | const RodEditorNotif_onCompositionOpen* = "RodEditorNotif_onCompositionOpen"
91 | const RodEditorNotif_onCompositionAdd* = "RodEditorNotif_onCompositionAdd"
92 | const RodEditorNotif_onCompositionSave* = "RodEditorNotif_onCompositionSave"
93 | const RodEditorNotif_onCompositionSaveAs* = "RodEditorNotif_onCompositionSaveAs"
94 | const RodEditorNotif_onCompositionNew* = "RodEditorNotif_onCompositionNew"
95 | const RodEditorNotif_onConvertToComp* = "RodEditorNotif_onConvertToComp"
96 |
97 |
98 | # Pasteboard
99 | const rodPbComposition* = "rod.composition"
100 | const rodPbSprite* = "rod.sprite"
101 | const rodPbFiles* = "rod.files"
102 | const NodePboardKind* = "io.github.yglukhov.rod.node"
103 | const BezierPboardKind* = "io.github.yglukhov.rod.bezier"
104 |
105 | # Editor's nodes
106 | const EditorRootNodeName* = "[EditorRoot]"
107 |
108 | # Default open tabs
109 | const defaultTabs* = ["Inspector", "Tree", "EditScene Settings", "Assets" ]
110 |
--------------------------------------------------------------------------------
/rod/editor/scene/components/editor_component.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ event ]
2 | import rod / [ rod_types ]
3 |
4 | #will be called for all components
5 | method onDrawGizmo*(c: Component) {.base.} = discard
6 |
7 | #will be called only for components on selected node
8 | method onKeyDown*(c: Component, e: Event): bool {.base.} = discard
9 | method onKeyUp*(c: Component, e: Event): bool {.base.} = discard
10 | method onTouchDown*(c: Component, e: Event): bool {.base.} = discard
11 | method onTouchMove*(c: Component, e: Event): bool {.base.} = discard
12 | method onTouchUp*(c: Component, e: Event): bool {.base.} = discard
13 |
--------------------------------------------------------------------------------
/rod/editor/scene/components/grid.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, matrixes, context, view, property_visitor ]
2 | import rod / [ rod_types, node, component, viewport ]
3 | import rod / tools / debug_draw
4 | import editor_component
5 | import math
6 |
7 | type EditorGrid* = ref object of RenderComponent
8 | gridSize: Size
9 | offset: Point
10 | snap: bool
11 |
12 | method componentNodeWasAddedToSceneView(c: EditorGrid) =
13 | c.gridSize = newSize(100, 100)
14 |
15 | method onDrawGizmo*(c: EditorGrid) =
16 | let scene = c.node.sceneView
17 | let ss = scene.bounds
18 | let p0 = scene.screenToWorldPoint(newVector3())
19 | let p1 = scene.screenToWorldPoint(newVector3(ss.width, ss.height))
20 |
21 | let ctx = currentContext()
22 |
23 | template drawGrid(r: Rect, s: Size, color: Color) =
24 | var gr = r
25 | gr.origin.x = gr.x - (s.width + (gr.x mod s.width))
26 | gr.origin.y = gr.y - (s.height + (gr.y mod s.height))
27 | gr.size.width += s.width * 2
28 | gr.size.height += s.height * 2
29 | ctx.strokeColor = color
30 |
31 | DDdrawGrid(gr, s, c.offset)
32 |
33 | var gr = newRect(p0.x, p0.y, p1.x - p0.x, p1.y - p0.y)
34 | drawGrid(gr, c.gridSize, newColor(0.0, 0.0, 0.0, 0.1))
35 | drawGrid(gr, newSize(c.gridSize.width * 5, c.gridSize.height * 5), newColor(0.0, 0.0, 0.0, 0.3))
36 |
37 | method visitProperties*(c: EditorGrid, p: var PropertyVisitor) =
38 | p.visitProperty("size", c.gridSize)
39 | if c.gridSize.width < 1.0:
40 | c.gridSize.width = 1.0
41 | if c.gridSize.height < 1.0:
42 | c.gridSize.height = 1.0
43 | p.visitProperty("offset", c.offset)
44 | p.visitProperty("snap", c.snap)
45 |
46 | proc snappingEnabled*(c: EditorGrid): bool = c.snap
47 |
48 | proc snappedWorldPosition*(c: EditorGrid, p: Vector3): Vector3 =
49 | let x1 = splitDecimal((p.x - c.offset.x) / c.gridSize.width).floatPart
50 | let y1 = splitDecimal((p.y - c.offset.y) / c.gridSize.height).floatPart
51 | let xo = if x1 < 0.5: - c.gridSize.width * x1 else: (1 - x1) * c.gridSize.width
52 | let yo = if y1 < 0.5: - c.gridSize.height * y1 else: (1 - y1) * c.gridSize.height
53 | result = newVector3(p.x + xo, p.y + yo)
54 |
55 | registerComponent(EditorGrid, "Editor")
56 |
--------------------------------------------------------------------------------
/rod/editor/scene/components/viewport_rect.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types ]
2 | import rod / [ rod_types, node, component, viewport ]
3 | import rod / tools / debug_draw
4 | import editor_component
5 |
6 | type ViewportRect* = ref object of RenderComponent
7 |
8 | method onDrawGizmo*(c: ViewportRect) =
9 | let scene = c.node.sceneView
10 | DDdrawRect(newRect(0, 0, scene.camera.viewportSize.width, scene.camera.viewportSize.height))
11 |
12 | registerComponent(ViewportRect, "Editor")
13 |
--------------------------------------------------------------------------------
/rod/editor/scene/editor_camera_controller.nim:
--------------------------------------------------------------------------------
1 | import nimx/[matrixes, types, event, view]
2 | import rod/[viewport, rod_types, node, quaternion ]
3 |
4 | type EditorCameraController* = ref object
5 | camera*: Node
6 | editedView: SceneView
7 | camPivot: Node
8 | camAnchor: Node
9 |
10 | startPos: Vector3
11 | startPivotPos: Vector3
12 | startPivotRot: Quaternion
13 | startAngle: Vector3
14 | currentAngle: Vector3
15 | currNode: Node
16 | currKey: VirtualKey
17 | currMouseKey: VirtualKey
18 | prev_x, prev_y: float
19 |
20 |
21 | proc calculatePivotPos(camNode: Node): Vector3 =
22 | var dir = newVector3(0, 0, -1)
23 | dir = camNode.worldTransform().transformDirection(dir)
24 | dir.normalize()
25 | dir = dir * 100.0
26 |
27 | result = camNode.worldPos + dir
28 |
29 | proc setCamera*(cc: EditorCameraController, camNode: Node) =
30 | cc.camPivot.worldPos = calculatePivotPos(camNode)
31 |
32 | var scale: Vector3
33 | var rot: Vector4
34 | discard camNode.worldTransform.tryGetScaleRotationFromModel(scale, rot)
35 | cc.camPivot.rotation = newQuaternion(rot.x, rot.y, rot.z, rot.w)
36 |
37 | cc.camAnchor.worldPos = camNode.worldPos
38 | cc.startPos = cc.camAnchor.worldPos
39 | cc.startPivotPos = cc.camPivot.worldPos
40 | cc.startPivotRot = cc.camPivot.rotation
41 |
42 | var q = cc.camPivot.rotation
43 | q.w = -q.w
44 | cc.currentAngle = q.eulerAngles()
45 | cc.startAngle = cc.currentAngle
46 |
47 | proc newEditorCameraController*(view: SceneView) : EditorCameraController =
48 | new(result)
49 | result.editedView = view
50 | result.camPivot = newNode("EditorCameraPivot")
51 | result.camAnchor = newNode("EditorCameraAnchor")
52 | result.currentAngle = newVector3(0)
53 |
54 | result.editedView.rootNode.addChild(result.camPivot)
55 | result.camPivot.addChild(result.camAnchor)
56 |
57 | result.setCamera(result.editedView.camera.node)
58 |
59 | proc updateCamera(cc: EditorCameraController) =
60 | var worldMat = cc.camAnchor.worldTransform()
61 | var pos: Vector3
62 | var scale: Vector3
63 | var rot: Vector4
64 | discard worldMat.tryGetTranslationFromModel(pos)
65 | discard worldMat.tryGetScaleRotationFromModel(scale, rot)
66 |
67 | cc.editedView.camera.node.worldPos = pos
68 | cc.editedView.camera.node.rotation = newQuaternion(rot.x, rot.y, rot.z, rot.w)
69 |
70 | proc setToNode*(cc: EditorCameraController, n: Node) =
71 | cc.currNode = n
72 | if not cc.currNode.isNil and (cc.currNode != cc.editedView.camera.node):
73 | cc.camPivot.worldPos = cc.currNode.worldPos
74 |
75 | cc.updateCamera()
76 |
77 | proc onTapDown*(cc: EditorCameraController, e : var Event) =
78 | cc.camAnchor.worldPos = cc.editedView.camera.node.worldPos
79 | cc.currMouseKey = e.keyCode
80 | cc.prev_x = 0.0
81 | cc.prev_y = 0.0
82 |
83 | proc onTapUp*(cc: EditorCameraController, dx, dy : float32, e : var Event) =
84 | cc.currMouseKey = 0.VirtualKey
85 |
86 | proc onKeyDown*(cc: EditorCameraController, e: var Event) =
87 | cc.currKey = e.keyCode
88 |
89 | proc onKeyUp*(cc: EditorCameraController, e: var Event) =
90 | if e.keyCode == VirtualKey.R:
91 | cc.camPivot.rotation = cc.startPivotRot
92 | cc.camPivot.worldPos = cc.startPivotPos
93 | cc.camAnchor.worldPos = cc.startPos
94 | cc.currentAngle = cc.startAngle
95 |
96 | cc.updateCamera()
97 |
98 | cc.currKey = 0.VirtualKey
99 |
100 | proc onScrollProgress*(cc: EditorCameraController, dx, dy : float, e : var Event) =
101 | if cc.editedView.camera.projectionMode == cpOrtho:
102 | if cc.currMouseKey == VirtualKey.MouseButtonSecondary:
103 |
104 | var shift_pos = newVector3(cc.prev_x - dx, cc.prev_y - dy, 0.0) * cc.editedView.camera.node.scale
105 | cc.prev_x = dx
106 | cc.prev_y = dy
107 | cc.camPivot.worldPos = cc.camPivot.worldPos + shift_pos
108 | cc.updateCamera()
109 |
110 | return
111 |
112 | if cc.currKey == VirtualKey.LeftAlt or cc.currKey == VirtualKey.RightAlt:
113 | cc.currentAngle.x += cc.prev_y - dy
114 | cc.currentAngle.y += cc.prev_x - dx
115 |
116 | let q = newQuaternionFromEulerXYZ(cc.currentAngle.x, cc.currentAngle.y, cc.currentAngle.z)
117 | cc.camPivot.rotation = q
118 |
119 | if cc.currMouseKey == VirtualKey.MouseButtonMiddle:
120 | var speed = 0.1
121 | if cc.currKey == VirtualKey.LeftShift:
122 | speed = 1.0
123 |
124 | var shift_pos = newVector3(cc.prev_x - dx, -cc.prev_y + dy, 0.0) * speed
125 | var rotMat = cc.camPivot.rotation.toMatrix4()
126 | rotMat.multiply(shift_pos, shift_pos)
127 |
128 | cc.camPivot.worldPos = cc.camPivot.worldPos + shift_pos
129 |
130 | cc.prev_x = dx
131 | cc.prev_y = dy
132 |
133 | cc.updateCamera()
134 |
135 | proc onMouseScrroll*(cc: EditorCameraController, e : var Event) =
136 | var dir: Vector3 = cc.camPivot.worldPos - cc.camAnchor.worldPos
137 | let ndir: Vector3 = normalized(dir)
138 | let offset: Vector3 = ndir * e.offset.y - ndir * e.offset.x * 10.0
139 |
140 | if cc.editedView.camera.projectionMode == cpOrtho:
141 | cc.editedView.camera.node.scale = cc.editedView.camera.node.scale + cc.editedView.camera.node.scale * e.offset.y * 0.005
142 | if cc.editedView.camera.node.scale.x < 0.01:
143 | cc.editedView.camera.node.scale = newVector3(0.01, 0.01, 0.01)
144 | elif cc.editedView.camera.node.scale.x > 100.0:
145 | cc.editedView.camera.node.scale = newVector3(100, 100, 100)
146 | return
147 |
148 | # 0 coord lock protection
149 | if length(offset) > length(dir) - 0.1 and dot(offset, dir) > 0.5:
150 | return
151 |
152 | cc.camAnchor.worldPos = cc.camAnchor.worldPos + offset
153 |
154 | cc.updateCamera()
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/rod/editor/scene/editor_scene_settings.nim:
--------------------------------------------------------------------------------
1 | import nimx/[view, text_field, button, popup_button,
2 | scroll_view, linear_layout, slider,
3 | property_visitor, expanding_view
4 | ]
5 | import rod/[node, component, rod_types, viewport]
6 | import rod/property_editors/[propedit_registry, standard_editors]
7 | import rod/component/camera
8 | import rod/edit_view
9 | import rod/editor/scene/components/grid
10 | import tables
11 | import variant
12 |
13 | export view
14 |
15 | type SceneSettingsView* = ref object of EditorTabView
16 | propView: LinearLayout
17 | scView: ScrollView
18 | currCameras: seq[Node]
19 | currSceneView: SceneView
20 | autoUpdate: bool
21 |
22 | proc reloadEditScene(v: SceneSettingsView) {.gcsafe.}
23 |
24 | method init*(i: SceneSettingsView, r: Rect) =
25 | procCall i.View.init(r)
26 | i.resizingMask = "wh"
27 |
28 | i.propView = newVerticalLayout(newRect(0, 20, i.bounds.width, 20))
29 | i.propView.resizingMask = "wb"
30 | i.propView.topMargin = 5
31 | i.propView.bottomMargin = 5
32 | i.propView.leftMargin = 5
33 | i.propView.rightMargin = 5
34 |
35 | i.scView = newScrollView(i.propView)
36 | i.scView.horizontalScrollBar = nil
37 | i.scView.resizingMask = "wh"
38 | i.scView.setFrame(newRect(0.0, 0.0, i.bounds.width, i.bounds.height - 20.0))
39 | i.addSubview(i.scView)
40 |
41 | i.reloadEditScene()
42 |
43 | proc inspect(i: SceneSettingsView, expView: var ExpandingView, visitor: var PropertyVisitor, comps: seq[Component]) =
44 | for v in comps:
45 | expView = newExpandingView(newRect(0, 0, 328, 20.0))
46 | expView.title = v.className
47 | expView.expand()
48 |
49 | v.visitProperties(visitor)
50 | i.propView.addSubview(expView)
51 |
52 | proc `inspectedNode=`*(i: SceneSettingsView, n: Node) =
53 | let scrollBar = i.scView.verticalScrollBar()
54 | let oldPos = scrollBar.value()
55 |
56 | while i.propView.subviews.len() > 1:
57 | i.propView.subviews[1].removeFromSuperview()
58 |
59 | if not n.isNil:
60 | proc changeSceneSettingsView() =
61 | i.inspectedNode = n
62 |
63 | var expView= newExpandingView(newRect(0, 50, 328, 20.0))
64 | expView.title = "Camera node"
65 | expView.expand()
66 |
67 | var visitor : PropertyVisitor
68 | visitor.requireName = true
69 | visitor.requireSetter = true
70 | visitor.requireGetter = true
71 | visitor.flags = { pfEditable }
72 | visitor.commit = proc() =
73 | let propView = propertyEditorForProperty(n, visitor.name, visitor.setterAndGetter, nil, changeSceneSettingsView)
74 | propView.autoresizingMask = {afFlexibleWidth}
75 | let propHolder = newView(propView.frame)
76 | propHolder.addSubview(propView)
77 | expView.addContent(propHolder)
78 |
79 | n.visitProperties(visitor)
80 | i.propView.addSubview(expView)
81 |
82 | var comps: seq[Component]
83 | for c in n.components:
84 | comps.add(c)
85 |
86 | var gridComp = n.sceneView.rootNode.componentIfAvailable(EditorGrid)
87 | if not gridComp.isNil:
88 | comps.add(gridComp)
89 |
90 | i.inspect(expView, visitor, comps)
91 |
92 | scrollBar.value = oldPos
93 | scrollBar.sendAction()
94 | else:
95 | discard
96 |
97 | method tabSize*(v: SceneSettingsView, bounds: Rect): Size=
98 | result = newSize(300.0, bounds.height)
99 |
100 | method tabAnchor*(v: SceneSettingsView): EditorTabAnchor =
101 | result = etaRight
102 |
103 | proc getAllCameras(n: Node): seq[Node]=
104 | result = @[]
105 | if not n.componentIfAvailable(Camera).isNil:
106 | result.add(n)
107 |
108 | for ch in n.children:
109 | result.add(ch.getAllCameras)
110 |
111 | proc reloadEditScene(v: SceneSettingsView) {.gcsafe.} =
112 | while v.propView.subviews.len > 0:
113 | v.propView.subviews[0].removeFromSuperview()
114 |
115 | var curScene: SceneView
116 | var root: Node
117 | var curCameraNode: Node
118 |
119 | if not v.editor.currentComposition.isNil:
120 | curScene = v.editor.currentComposition.rootNode.sceneView
121 | else:
122 | if v.editor.sceneView.isNil: return
123 | curScene = v.editor.sceneView
124 |
125 | root = curScene.rootNode
126 | curCameraNode = curScene.camera.node
127 |
128 | var cameraNodes = root.getAllCameras()
129 | var cameraNames = newSeq[string]()
130 | for c in cameraNodes:
131 | cameraNames.add(c.name)
132 |
133 | var curCameraIdx = cameraNodes.find(curCameraNode)
134 | if curCameraIdx < 0: return
135 | v.currSceneView = curScene
136 | v.currCameras = cameraNodes
137 | var cameraSel = PopupButton.new(newRect(10, 10, v.bounds.width, 20))
138 | cameraSel.items = cameraNames
139 | cameraSel.selectedIndex = curCameraIdx
140 | v.propView.addSubview(cameraSel)
141 | cameraSel.onAction do():
142 | let camn = cameraNodes[cameraSel.selectedIndex]
143 | curScene.camera = camn.component(Camera)
144 | v.inspectedNode = camn
145 |
146 | v.inspectedNode = curCameraNode
147 |
148 | method onCompositionChanged*(v: SceneSettingsView, comp: CompositionDocument) =
149 | procCall v.EditorTabView.onCompositionChanged(comp)
150 | v.reloadEditScene()
151 |
152 | method update*(v: SceneSettingsView)=
153 | let cams = v.currSceneView.rootNode.getAllCameras()
154 | if cams != v.currCameras:
155 | v.reloadEditScene()
156 | echo "reload cameras"
157 |
158 | registerEditorTab("EditScene Settings", SceneSettingsView)
159 |
--------------------------------------------------------------------------------
/rod/editor/scene/gizmo.nim:
--------------------------------------------------------------------------------
1 | import nimx/[context, portable_gl, types, matrixes, event]
2 | import rod/[node, viewport]
3 |
4 | type Gizmo* = ref object of RootObj
5 | gizmoNode*: Node
6 | axisMask*: Vector3
7 | mEditedNode*: Node
8 | mPrevCastedAxis: Node
9 |
10 | method updateGizmo*(g: Gizmo) {.base.} = discard
11 | method startTransform*(g: Gizmo, selectedGizmo: Node, position: Point) {.base.} = discard
12 | method proccesTransform*(g: Gizmo, position: Point) {.base.} = discard
13 | method stopTransform*(g: Gizmo) {.base.} = discard
14 | method onMouseIn*(g: Gizmo, castedNode: Node) {.base.} = discard
15 | method onMouseOut*(g: Gizmo, castedNode: Node) {.base.} = discard
16 |
17 | proc newGizmo*(): Gizmo =
18 | result = new(Gizmo)
19 | result.gizmoNode = newNode()
20 | result.gizmoNode.alpha = 0.0
21 |
22 | proc `editedNode=`*(g: Gizmo, n: Node) =
23 | g.mEditedNode = n
24 | if not n.isNil:
25 | g.gizmoNode.alpha = 1.0
26 | g.updateGizmo()
27 | else:
28 | g.gizmoNode.alpha = 0.0
29 |
30 | proc editedNode*(g: Gizmo): Node = g.mEditedNode
31 |
32 | proc onTouchEv*(g: Gizmo, e: var Event): bool =
33 | case e.buttonState:
34 | of bsUp:
35 | g.stopTransform()
36 |
37 | of bsDown:
38 | let castedGizmo = g.gizmoNode.sceneView.rayCastFirstNode(g.gizmoNode, e.localPosition)
39 | if not castedGizmo.isNil:
40 | result = true
41 | g.startTransform(castedGizmo, e.localPosition)
42 |
43 | of bsUnknown:
44 | g.proccesTransform(e.localPosition)
45 |
46 | proc onMouseOver*(g: Gizmo, e: var Event) =
47 | if g.isNil or g.gizmoNode.sceneView.isNil: return
48 |
49 | let castedGizmo = g.gizmoNode.sceneView.rayCastFirstNode(g.gizmoNode, e.localPosition)
50 |
51 | if castedGizmo != g.mPrevCastedAxis:
52 | if not g.mPrevCastedAxis.isNil:
53 | g.onMouseOut(g.mPrevCastedAxis)
54 | if not castedGizmo.isNil:
55 | g.onMouseIn(castedGizmo)
56 |
57 | g.mPrevCastedAxis = castedGizmo
58 |
--------------------------------------------------------------------------------
/rod/editor/scene/gizmo_move.nim:
--------------------------------------------------------------------------------
1 | import nimx/[context, portable_gl, types, matrixes, event]
2 | import rod/component/[primitives/cone, primitives/cube, mesh_component, material]
3 | import rod/[node, viewport, quaternion, editor/scene/gizmo]
4 | import rod/editor/scene/components/grid
5 | import strutils
6 |
7 | type MoveGizmo* = ref object of Gizmo
8 | screenPoint: Vector3
9 | offset: Vector3
10 | mGrid: EditorGrid
11 |
12 | proc grid(c: MoveGizmo): EditorGrid =
13 | if c.mGrid.isNil:
14 | c.mGrid = c.gizmoNode.sceneView.rootNode.componentIfAvailable(EditorGrid)
15 | result = c.mGrid
16 |
17 | method updateGizmo*(g: MoveGizmo) =
18 | if g.mEditedNode.isNil:
19 | return
20 |
21 | g.gizmoNode.position = g.mEditedNode.worldPos
22 |
23 | var dist = (g.gizmoNode.sceneView.camera.node.worldPos - g.gizmoNode.worldPos).length
24 | let wp1 = g.gizmoNode.sceneView.camera.node.worldTransform() * newVector3(0.0, 0.0, -dist)
25 | let wp2 = g.gizmoNode.sceneView.camera.node.worldTransform() * newVector3(100.0, 0.0, -dist)
26 |
27 | let p1 = g.gizmoNode.sceneView.worldToScreenPoint(wp1)
28 | let p2 = g.gizmoNode.sceneView.worldToScreenPoint(wp2)
29 |
30 | let cameraScale = g.gizmoNode.sceneView.camera.node.scale
31 | let scale = 450.0 / abs(p2.x - p1.x) * cameraScale.x
32 | g.gizmoNode.scale = newVector3(scale, scale, scale)
33 |
34 |
35 | proc getAxisColor(name: string): Color =
36 | if name.contains("_x"):
37 | result = newColor(1, 0, 0, 1)
38 | elif name.contains("_y"):
39 | result = newColor(0, 1, 0, 1)
40 | elif name.contains("_z"):
41 | result = newColor(0, 0, 1, 0.3)
42 |
43 | proc createPlane(name: string): Node =
44 | result = newNode(name)
45 | let size = 4.0
46 | let cubeNode = newNode()
47 | result.addChild(cubeNode)
48 |
49 | cubeNode.positionX = size
50 | cubeNode.positionY = size
51 | cubeNode.alpha = 0.8
52 |
53 | let cube = cubeNode.addComponent(CubeComponent)
54 | cube.size = newVector3(size, size, 0.1)
55 | cube.material.diffuse = getAxisColor(name)
56 |
57 | proc createArrow(name: string): Node =
58 | result = newNode(name)
59 | let h = 16.0
60 | let cylNode = newNode()
61 | let coneNode = newNode()
62 |
63 | coneNode.positionY = h
64 |
65 | result.addChild(cylNode)
66 | result.addChild(coneNode)
67 |
68 | let cylinder = cylNode.addComponent(ConeComponent)
69 | cylinder.height = h
70 | cylinder.material.diffuse = getAxisColor(name)
71 | cylinder.radius1 = 0.6
72 | cylinder.radius2 = 0.6
73 |
74 | let cone = coneNode.addComponent(ConeComponent)
75 | cone.radius1 = 1.5
76 | cone.radius2 = 0.0
77 | cone.material.diffuse = getAxisColor(name)
78 |
79 | proc createMesh(): Node =
80 | result = newNode("gizmo_axis")
81 |
82 | let axis_x = createArrow("axis_x")
83 | let axis_y = createArrow("axis_y")
84 | let axis_z = createArrow("axis_z")
85 | result.addChild(axis_x)
86 | result.addChild(axis_y)
87 | result.addChild(axis_z)
88 |
89 | axis_x.rotation = newQuaternionFromEulerYXZ(0, 0, -90)
90 | axis_z.rotation = newQuaternionFromEulerYXZ(90, 0, 0)
91 |
92 | let plane_x = createPlane("plane_x")
93 | let plane_y = createPlane("plane_y")
94 | let plane_z = createPlane("plane_z")
95 | result.addChild(plane_x)
96 | result.addChild(plane_y)
97 | result.addChild(plane_z)
98 |
99 | plane_x.rotation = newQuaternionFromEulerYXZ(0, -90, 0)
100 | plane_y.rotation = newQuaternionFromEulerYXZ(90, 0, 0)
101 |
102 |
103 | proc newMoveGizmo*(): MoveGizmo =
104 | result = new(MoveGizmo)
105 | result.gizmoNode = createMesh()
106 | # result.gizmoNode.loadComposition( getMoveAxisJson() )
107 | result.gizmoNode.alpha = 0.0
108 |
109 | method startTransform*(ga: MoveGizmo, selectedGizmo: Node, position: Point) =
110 | let axis = selectedGizmo.parent
111 | if axis.name.contains("axis_x"):
112 | ga.axisMask = newVector3(1.0, 0.0, 0.0)
113 | elif axis.name.contains("axis_y"):
114 | ga.axisMask = newVector3(0.0, 1.0, 0.0)
115 | elif axis.name.contains("axis_z"):
116 | ga.axisMask = newVector3(0.0, 0.0, 1.0)
117 |
118 | elif axis.name.contains("plane_x"):
119 | ga.axisMask = newVector3(0.0, 1.0, 1.0)
120 | elif axis.name.contains("plane_y"):
121 | ga.axisMask = newVector3(1.0, 0.0, 1.0)
122 | elif axis.name.contains("plane_z"):
123 | ga.axisMask = newVector3(1.0, 1.0, 0.0)
124 |
125 | ga.screenPoint = ga.gizmoNode.sceneView.worldToScreenPoint(ga.gizmoNode.worldPos)
126 | ga.offset = ga.gizmoNode.worldPos - ga.gizmoNode.sceneView.screenToWorldPoint(newVector3(position.x, position.y, ga.screenPoint.z))
127 |
128 | method proccesTransform*(g: MoveGizmo, position: Point) =
129 | if g.mEditedNode.isNil:
130 | return
131 |
132 | let curScreenPoint = newVector3(position.x, position.y, g.screenPoint.z)
133 | var curPosition: Vector3
134 | curPosition = g.mEditedNode.sceneView.screenToWorldPoint(curScreenPoint) + g.offset
135 | curPosition = curPosition - g.gizmoNode.worldPos
136 | g.gizmoNode.position = g.gizmoNode.worldPos + curPosition * g.axisMask
137 | if not g.mEditedNode.parent.isNil:
138 | try:
139 | var p = g.gizmoNode.position
140 | if not g.grid.isNil and g.grid.snappingEnabled:
141 | p = g.grid.snappedWorldPosition(p)
142 | g.mEditedNode.position = g.mEditedNode.parent.worldToLocal(p)
143 | except:
144 | discard
145 | else:
146 | g.mEditedNode.position = g.gizmoNode.position
147 |
148 | method stopTransform*(g: MoveGizmo) =
149 | g.axisMask = newVector3(0.0, 0.0, 0.0)
150 |
151 | method onMouseIn*(g: MoveGizmo, castedNode: Node) =
152 | for ch in castedNode.parent.children:
153 | ch.getComponent(MeshComponent).material.diffuse = newColor(1, 1, 0, 1)
154 |
155 | method onMouseOut*(g: MoveGizmo, castedNode: Node) =
156 | let parent = castedNode.parent
157 | for ch in parent.children:
158 | ch.getComponent(MeshComponent).material.diffuse = getAxisColor(parent.name)
159 |
--------------------------------------------------------------------------------
/rod/editor/scene/node_selector.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ context, portable_gl, types, matrixes ]
2 | import rod / [ component, node, viewport ]
3 | import opengl
4 |
5 | const vertexShader = """
6 | attribute vec4 aPosition;
7 | uniform mat4 mvpMatrix;
8 | void main() { gl_Position = mvpMatrix * vec4(aPosition.xyz, 1.0); }
9 | """
10 | const fragmentShader = """
11 | #ifdef GL_ES
12 | #extension GL_OES_standard_derivatives : enable
13 | precision mediump float;
14 | #endif
15 | uniform vec4 uColor;
16 | void main() { gl_FragColor = uColor; }
17 | """
18 |
19 | # let vertexData = [-0.5.GLfloat,-0.5, 0.5, 0.5,-0.5, 0.5, 0.5,0.5, 0.5, -0.5,0.5, 0.5,
20 | # -0.5 ,-0.5,-0.5, 0.5,-0.5,-0.5, 0.5,0.5,-0.5, -0.5,0.5,-0.5]
21 | let indexData = [0.GLushort, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 3, 7, 2, 6, 0, 4, 1, 5]
22 |
23 | var selectorSharedIndexBuffer: BufferRef
24 | var selectorSharedVertexBuffer: BufferRef
25 | var selectorSharedNumberOfIndexes: GLsizei
26 | var selectorSharedShader: ProgramRef
27 |
28 | type Attrib = enum
29 | aPosition
30 |
31 | type NodeSelector* = ref object
32 | mSelectedNode: Node
33 | vertexData: seq[GLfloat]
34 | color*: Color
35 |
36 | proc createVBO(ns: NodeSelector) =
37 | let c = currentContext()
38 | let gl = c.gl
39 |
40 | if selectorSharedIndexBuffer == invalidBuffer:
41 | selectorSharedIndexBuffer = gl.createBuffer()
42 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, selectorSharedIndexBuffer)
43 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW)
44 |
45 | if selectorSharedVertexBuffer == invalidBuffer:
46 | selectorSharedVertexBuffer = gl.createBuffer()
47 | gl.bindBuffer(gl.ARRAY_BUFFER, selectorSharedVertexBuffer)
48 | gl.bufferData(gl.ARRAY_BUFFER, ns.vertexData, gl.STATIC_DRAW)
49 | selectorSharedNumberOfIndexes = indexData.len.GLsizei
50 |
51 | gl.bindBuffer(gl.ARRAY_BUFFER, invalidBuffer)
52 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, invalidBuffer)
53 |
54 | proc newNodeSelector*(): NodeSelector =
55 | result.new()
56 | result.color = newColor(0.12, 1, 0, 1)
57 |
58 | proc createBoxes(ns: NodeSelector) =
59 | ns.vertexData.setLen(0)
60 | let bbox = ns.mSelectedNode.nodeBounds()
61 | if not bbox.isEmpty:
62 | ns.vertexData.add([bbox.minPoint.x, bbox.minPoint.y, bbox.minPoint.z])
63 | ns.vertexData.add([bbox.maxPoint.x, bbox.minPoint.y, bbox.minPoint.z])
64 | ns.vertexData.add([bbox.maxPoint.x, bbox.maxPoint.y, bbox.minPoint.z])
65 | ns.vertexData.add([bbox.minPoint.x, bbox.maxPoint.y, bbox.minPoint.z])
66 |
67 | ns.vertexData.add([bbox.minPoint.x, bbox.minPoint.y, bbox.maxPoint.z])
68 | ns.vertexData.add([bbox.maxPoint.x, bbox.minPoint.y, bbox.maxPoint.z])
69 | ns.vertexData.add([bbox.maxPoint.x, bbox.maxPoint.y, bbox.maxPoint.z])
70 | ns.vertexData.add([bbox.minPoint.x, bbox.maxPoint.y, bbox.maxPoint.z])
71 |
72 | ns.createVBO()
73 |
74 | proc `selectedNode=`*(ns: NodeSelector, n: Node) =
75 | ns.mSelectedNode = n
76 |
77 | proc draw*(ns: NodeSelector) =
78 | let node = ns.mSelectedNode
79 | if not node.isNil:
80 | ns.createBoxes()
81 | let c = currentContext()
82 | let gl = c.gl
83 |
84 | if selectorSharedShader == invalidProgram:
85 | selectorSharedShader = gl.newShaderProgram(vertexShader, fragmentShader, [(aPosition.GLuint, $aPosition)])
86 | if selectorSharedShader == invalidProgram:
87 | return
88 |
89 | gl.enable(gl.DEPTH_TEST)
90 | gl.bindBuffer(gl.ARRAY_BUFFER, selectorSharedVertexBuffer)
91 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, selectorSharedIndexBuffer)
92 |
93 | gl.enableVertexAttribArray(aPosition.GLuint)
94 | gl.vertexAttribPointer(aPosition.GLuint, 3.GLint, gl.FLOAT, false, (3 * sizeof(GLfloat)).GLsizei , 0)
95 |
96 | gl.useProgram(selectorSharedShader)
97 |
98 | c.setColorUniform(selectorSharedShader, "uColor", ns.color)
99 |
100 | let vp = node.sceneView
101 | if not vp.isNil:
102 | let mvpMatrix = vp.getViewProjectionMatrix()
103 | gl.uniformMatrix4fv(gl.getUniformLocation(selectorSharedShader, "mvpMatrix"), false, mvpMatrix)
104 |
105 | when not defined(js): glLineWidth(2.0)
106 | gl.drawElements(gl.LINES, selectorSharedNumberOfIndexes, gl.UNSIGNED_SHORT)
107 | when not defined(js): glLineWidth(1.0)
108 |
109 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, invalidBuffer)
110 | gl.bindBuffer(gl.ARRAY_BUFFER, invalidBuffer)
111 |
112 | #TODO to default settings
113 | gl.disable(gl.DEPTH_TEST)
114 |
--------------------------------------------------------------------------------
/rod/material/shader.nim:
--------------------------------------------------------------------------------
1 | import math, tables, hashes, sets, variant
2 | import nimx/[types, portable_gl, context]
3 |
4 | type Shader* = ref object
5 | vertShader*, fragShader*: string
6 | program: ProgramRef
7 | shaderMacroFlags: HashSet[string]
8 | shadersCache: Table[HashSet[string], tuple[shader: ProgramRef, refCount: int]]
9 | attributes: seq[tuple[index: GLuint, name: string]]
10 |
11 | needUpdate: bool
12 |
13 | # var shadersCache = initTable[HashSet[string], tuple[shader: ProgramRef, refCount: int]]()
14 |
15 | proc hash(sm: HashSet[string]): Hash =
16 | var sum = ""
17 | for macros in sm:
18 | sum &= macros
19 | result = sum.hash()
20 | result = !$result
21 |
22 | proc createShader(sh: Shader) =
23 | if currentContext().isNil:
24 | return
25 |
26 | let gl = currentContext().gl
27 | if not sh.shadersCache.contains(sh.shaderMacroFlags):
28 | var commonShaderDefines = ""
29 |
30 | var vShader = sh.vertShader
31 | var fShader = sh.fragShader
32 |
33 | for mcrs in sh.shaderMacroFlags:
34 | commonShaderDefines &= """#define """ & mcrs & "\n"
35 |
36 | vShader = commonShaderDefines & vShader
37 | fShader = commonShaderDefines & fShader
38 |
39 | sh.program = gl.newShaderProgram(vShader, fShader, sh.attributes)
40 | sh.needUpdate = false
41 |
42 | sh.shadersCache[sh.shaderMacroFlags] = (sh.program, 1)
43 |
44 | else:
45 | sh.program = sh.shadersCache[sh.shaderMacroFlags].shader
46 | sh.shadersCache[sh.shaderMacroFlags].refCount += 1
47 | sh.needUpdate = false
48 |
49 | proc finalize(s: Shader) =
50 | if s.program != invalidProgram:
51 | currentContext().gl.deleteProgram(s.program)
52 |
53 | proc newShader*(vs, fs: string, attributes: seq[tuple[index: GLuint, name: string]]): Shader =
54 | result.new(finalize)
55 | result.vertShader = vs
56 | result.fragShader = fs
57 | result.attributes = attributes
58 | result.needUpdate = true
59 | result.shaderMacroFlags = initHashSet[string]()
60 | result.shadersCache = initTable[HashSet[string], tuple[shader: ProgramRef, refCount: int]]()
61 |
62 | result.createShader()
63 |
64 | proc bindAttribLocation*(sh: Shader, index: GLuint, name: string) =
65 | currentContext().gl.bindAttribLocation(sh.program, index, name)
66 |
67 | proc addDefine*(sh: Shader, def: string) =
68 | sh.shaderMacroFlags.incl(def)
69 | sh.needUpdate = true
70 |
71 | proc removeDefine*(sh: Shader, def: string) =
72 | sh.shaderMacroFlags.excl(def)
73 | sh.needUpdate = true
74 |
75 | proc bindShader*(sh: Shader) =
76 | if sh.needUpdate:
77 | sh.createShader()
78 |
79 | if sh.needUpdate == true:
80 | echo "ERROR! Try to use not created shader program"
81 | return
82 |
83 | let gl = currentContext().gl
84 | gl.useProgram(sh.program)
85 |
86 | proc setUniform*(sh: Shader, name: string, uniform: int) =
87 | let gl = currentContext().gl
88 | gl.uniform1i(gl.getUniformLocation(sh.program, name), uniform.GLint)
89 |
90 | proc setUniform*(sh: Shader, name: string, uniform: float) =
91 | let gl = currentContext().gl
92 | gl.uniform1f(gl.getUniformLocation(sh.program, name), uniform)
93 |
94 | proc setUniform*(sh: Shader, name: string, uniform: Vector2) =
95 | let gl = currentContext().gl
96 | gl.uniform2fv(gl.getUniformLocation(sh.program, name), uniform)
97 |
98 | proc setUniform*(sh: Shader, name: string, uniform: Vector3) =
99 | let gl = currentContext().gl
100 | gl.uniform3fv(gl.getUniformLocation(sh.program, name), uniform)
101 |
102 | proc setUniform*(sh: Shader, name: string, uniform: Vector4) =
103 | let gl = currentContext().gl
104 | gl.uniform4fv(gl.getUniformLocation(sh.program, name), uniform)
105 |
106 | proc setUniform*(sh: Shader, name: string, uniform: Color) =
107 | let gl = currentContext().gl
108 | let arr = [uniform.r, uniform.g, uniform.b, uniform.a]
109 | gl.uniform4fv(gl.getUniformLocation(sh.program, name), arr)
110 |
111 | proc setUniform*(sh: Shader, name: string, uniform: Size) =
112 | let gl = currentContext().gl
113 | currentContext().setPointUniform(gl.getUniformLocation(sh.program, name), newPoint(uniform.width, uniform.height))
114 |
115 | proc setUniform*(sh: Shader, name: string, uniform: Matrix4) =
116 | let gl = currentContext().gl
117 | gl.uniformMatrix4fv(gl.getUniformLocation(sh.program, name), false, uniform)
118 |
119 | proc setTransformUniform*(sh: Shader) =
120 | currentContext().setTransformUniform(sh.program)
121 |
122 | proc setUniform[T](sh: Shader, name: string, uniform: T) =
123 | sh.setUniform(name, uniform)
124 |
125 |
126 | # static uniforms
127 | # this uniforms assign only one time
128 | # but they will setup automatically after bind shader
129 | var procRegistry = initTable[TypeId, proc(sh: Shader, name: string, v: Variant)]()
130 |
131 | proc registerProc[T]( setUniformProc: proc(sh: Shader, name: string, value: T) ) =
132 | procRegistry[getTypeId(T)] = proc(sh: Shader, name: string, v: Variant) =
133 | let value = v.get(T)
134 | sh.setUniformProc(name, value)
135 |
136 | proc addStaticUniform*[T](sh: Shader, name: string, uniform: T) =
137 | var u = newVariant(uniform)
138 | let setter = procRegistry.getOrDefault(u.typeId)
139 | sh.setter(name, u)
140 |
141 | registerProc(setUniform[float])
142 | registerProc(setUniform[int])
143 | registerProc(setUniform[Vector2])
144 | registerProc(setUniform[Vector3])
145 | registerProc(setUniform[Vector4])
146 | registerProc(setUniform[Color])
147 | registerProc(setUniform[Size])
148 | registerProc(setUniform[Matrix4])
149 |
--------------------------------------------------------------------------------
/rod/message_queue.nim:
--------------------------------------------------------------------------------
1 | import deques, hashes, macros
2 |
3 | type
4 | MessageId* = distinct int
5 | MessageQueue*[M] = ref object
6 | messages: Deque[tuple[id: MessageId, msg: M]]
7 |
8 | proc toMessageId*(str: string): MessageId = cast[MessageId](hash(str))
9 |
10 | proc `==`*(id: MessageId, str: string): bool = int(id) == int(str.toMessageId)
11 |
12 | proc `$`*(id: MessageId): string = $(int(id))
13 |
14 | proc createMessageQueue*[M](): MessageQueue[M] =
15 | result = new(MessageQueue[M])
16 | result.messages = initDeque[tuple[id: MessageId, msg: M]]()
17 |
18 | proc post*[M](q: MessageQueue[M], id: string, m: M = default(M)) =
19 | q.messages.addLast((id: id.toMessageId, msg: m))
20 |
21 | proc isEmpty*[M](q: MessageQueue[M]): bool = q.messages.len == 0
22 |
23 | iterator popChunk*[M](q: MessageQueue[M], chunk: int = 100): tuple[id: MessageId, msg:M] =
24 | var i = 0
25 | while q.messages.len > 0 and i < chunk:
26 | var v = q.messages.popFirst()
27 | yield v
28 | inc i
29 |
--------------------------------------------------------------------------------
/rod/postprocess_context.nim:
--------------------------------------------------------------------------------
1 | import nimx/[types, portable_gl]
2 | import rod/component
3 | import rod_types
4 |
5 | export PostprocessContext
6 |
7 | proc newPostprocessContext*(): PostprocessContext =
8 | result.new()
9 | result.shader = invalidProgram
10 | result.setupProc = proc(c: Component) = discard
11 | result.drawProc = proc(c: Component) = discard
12 |
--------------------------------------------------------------------------------
/rod/property_editors/propedit_registry.nim:
--------------------------------------------------------------------------------
1 | import nimx/view
2 | import nimx/property_editors/propedit_registry as npr
3 |
4 | export npr
5 |
6 | import rod/node
7 | import variant
8 |
9 | proc propertyEditorForProperty*(n: Node, title: string, v: Variant, onChangeCallback, changeInspectorCallback: proc() {.gcsafe.}): View =
10 | propertyEditorForProperty(newVariant(n), title, v, onChangeCallback, changeInspectorCallback)
11 |
--------------------------------------------------------------------------------
/rod/ray.nim:
--------------------------------------------------------------------------------
1 | import nimx/matrixes
2 | import nimx/types
3 | import rod/rod_types
4 |
5 | type RayCastInfo* = object
6 | node*: Node
7 | distance*: float32
8 | position*: Vector3
9 |
10 | type Ray* = object
11 | origin*: Vector3
12 | direction*: Vector3
13 |
14 | proc transform*(r:Ray, mat:Matrix4): Ray =
15 | result.direction.x = r.direction.x * mat[0] + r.direction.y * mat[4] + r.direction.z * mat[8]
16 | result.direction.y = r.direction.x * mat[1] + r.direction.y * mat[5] + r.direction.z * mat[9]
17 | result.direction.z = r.direction.x * mat[2] + r.direction.y * mat[6] + r.direction.z * mat[10]
18 | # result.direction.normalize()
19 |
20 | result.origin = mat * r.origin
21 |
22 | proc intersectWithPlane*(r: Ray, planeNormal: Vector3, dist: Coord, output: var Vector3): bool =
23 | var denom = dot(r.direction, planeNormal)
24 | if denom != 0:
25 | var t = -(dot(r.origin, planeNormal) + dist) / denom
26 | if t < 0:
27 | return false
28 | output = r.origin + r.direction * t
29 | return true
30 | elif dot(planeNormal, r.origin) + dist == 0:
31 | output = r.origin
32 | return true
33 | else:
34 | return false
35 |
36 | proc intersectWithPlane*(r: Ray, planeNormal, pointOnPlane: Vector3, output: var Vector3): bool =
37 | r.intersectWithPlane(planeNormal, -dot(planeNormal, pointOnPlane), output)
38 |
39 |
40 | proc intersectWithAABB*(r: Ray, minCoord, maxCoord: Vector3, distance: var float32): bool =
41 | # r.direction is unit direction vector of ray
42 | var dirfrac = newVector3(1.0 / r.direction.x, 1.0 / r.direction.y, 1.0 / r.direction.z)
43 |
44 | # minCoord is the corner of AABB with minimal coordinates - left bottom, maxCoord is maximal corner
45 | # r.origin is origin of ray
46 | let t1 = (minCoord.x - r.origin.x)*dirfrac.x
47 | let t2 = (maxCoord.x - r.origin.x)*dirfrac.x
48 | let t3 = (minCoord.y - r.origin.y)*dirfrac.y
49 | let t4 = (maxCoord.y - r.origin.y)*dirfrac.y
50 | let t5 = (minCoord.z - r.origin.z)*dirfrac.z
51 | let t6 = (maxCoord.z - r.origin.z)*dirfrac.z
52 |
53 | let tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
54 | let tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))
55 |
56 | # if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
57 | if tmax < 0.0 or tmin < 0.0:
58 | return false
59 |
60 | # if tmin > tmax, ray doesn't intersect AABB
61 | if tmin > tmax :
62 | return false
63 |
64 | distance = tmin
65 | return true
66 |
67 | proc intersectWithTriangle(r: Ray, v0, v1, v2: Vector3, distance: var float32): bool {.used.} =
68 | let edge1 = v1 - v0
69 | let edge2 = v2 - v0
70 | let pvec = cross(r.direction, edge2)
71 | var u, v: float32
72 |
73 | let det = dot(edge1, pvec) #edge1.x * pvec.x + edge1.y * pvec.y + edge1.z * pvec.z #edge1 & pvec;
74 | var tvec = newVector3(0.0)
75 | var qvec = newVector3(0.0)
76 |
77 | if det > 0.0001 :
78 | tvec = r.origin - v0
79 | u = dot(tvec, pvec) # tvec.x * pvec.x + tvec.y * pvec.y + tvec.z * pvec.z #tvec & pvec;
80 | if u < 0.0f or u > det :
81 | return false
82 |
83 | qvec = cross(tvec, edge1)
84 | v = dot(r.direction, qvec) #r.direction.x * qvec.x + r.direction.y * qvec.y + r.direction.z * qvec.z #dir & qvec;
85 | if v < 0.0f or u + v > det :
86 | return false
87 |
88 | elif det < -0.0001 :
89 | tvec = r.origin - v0
90 | u = dot(tvec, pvec) #tvec.x * pvec.x + tvec.y * pvec.y + tvec.z * pvec.z #tvec & pvec;
91 | if u > 0.0f or u < det :
92 | return false
93 |
94 | qvec = cross(tvec, edge1)
95 | v = dot(r.direction, qvec) #r.direction.x * qvec.x + r.direction.y * qvec.y + r.direction.z * qvec.z #dir & qvec;
96 | if v > 0.0f or u + v < det :
97 | return false
98 | else:
99 | return false
100 |
101 | distance = edge2.x * qvec.x + edge2.y * qvec.y + edge2.z * qvec.z # edge2 & qvec;
102 | var fInvDet = 1.0 / det
103 | distance *= fInvDet
104 | return true
105 |
--------------------------------------------------------------------------------
/rod/rod_types.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, matrixes, animation, view, image, portable_gl ]
2 | import rod / message_queue
3 | import quaternion
4 | import tables
5 | const maxLightsCount* = 8
6 |
7 | when defined(rodedit):
8 | import json
9 |
10 | type
11 | NodeFlags* = enum # Don't change the order!
12 | enabled
13 | affectsChildren # Should posteffects affect only this node or its children as well
14 | dirty
15 | serializable
16 |
17 | Node* = ref object
18 | mTranslation*: Vector3
19 | mRotation*: Quaternion
20 | mScale*: Vector3
21 | renderComponents*: seq[RenderComponent]
22 | scriptComponents*: seq[ScriptComponent]
23 | children*: seq[Node]
24 | mParent*: Node
25 | name*: string
26 | animations*: TableRef[string, Animation]
27 | mSceneView*: SceneView
28 | mMatrix*: Matrix4
29 | worldMatrix*: Matrix4
30 | alpha*: Coord
31 | composition*: Composition
32 | mAnchorPoint*: Vector3
33 | mFlags*: set[NodeFlags]
34 |
35 | when defined(rodedit):
36 | jAnimations*: JsonNode
37 |
38 | BBox* = object
39 | minPoint*: Vector3
40 | maxPoint*: Vector3
41 |
42 | Frustum* = BBox
43 |
44 | Component* = ref object of RootRef
45 | node*: Node
46 |
47 | ScriptComponent* = ref object of Component
48 | RenderComponent* = ref object of Component
49 |
50 | AnimationRunnerComponent* = ref object of ScriptComponent
51 | runner*: AnimationRunner
52 |
53 | PostprocessContext* = ref object
54 | shader*: ProgramRef
55 | setupProc*: proc(c: Component)
56 | drawProc*: proc(c: Component)
57 | depthImage*: SelfContainedImage
58 | depthMatrix*: Matrix4
59 |
60 | System* = ref object of RootRef
61 | sceneView*: SceneView
62 |
63 | SceneView* = ref object of View
64 | systems*: seq[System]
65 | viewMatrixCached*: Matrix4
66 | viewProjMatrix*: Matrix4
67 | mCamera*: Camera
68 | mRootNode*: Node
69 | animationRunners*: seq[AnimationRunner]
70 | lightSources*: TableRef[string, LightSource]
71 | uiComponents*: seq[UIComponent]
72 | postprocessContext*: PostprocessContext
73 | editing*: bool
74 | afterDrawProc*: proc() {.gcsafe.} # PRIVATE DO NOT USE!!!
75 |
76 | Composition* = ref object
77 | url*: string
78 | node*: Node
79 | when defined(rodedit):
80 | originalUrl*: string # used in editor to restore url
81 |
82 | CameraProjection* = enum
83 | cpOrtho,
84 | cpPerspective
85 |
86 | Camera* = ref object of ScriptComponent
87 | projectionMode*: CameraProjection
88 | zNear*, zFar*, fov*: Coord
89 | viewportSize*: Size
90 |
91 | UIComponent* = ref object of RenderComponent
92 | mView*: View
93 | mEnabled*: bool
94 |
95 | LightSource* = ref object of ScriptComponent
96 | mLightAmbient*: float32
97 | mLightDiffuse*: float32
98 | mLightSpecular*: float32
99 | mLightConstant*: float32
100 | mLightLinear*: float32
101 | mLightQuadratic*: float32
102 | mLightAttenuation*: float32
103 |
104 | mLightColor*: Color
105 |
106 | lightPosInited*: bool
107 | lightAmbientInited*: bool
108 | lightDiffuseInited*: bool
109 | lightSpecularInited*: bool
110 | lightConstantInited*: bool
111 | lightLinearInited*: bool
112 | lightQuadraticInited*: bool
113 | mLightAttenuationInited*: bool
114 |
--------------------------------------------------------------------------------
/rod/system/messaging.nim:
--------------------------------------------------------------------------------
1 | import rod / [ rod_types, message_queue, node, systems, viewport, component ]
2 | import strutils, typetraits, logging
3 |
4 | export message_queue, systems
5 |
6 | type
7 | NodeMessage = object
8 | path: string
9 | sender: Node
10 | data: string
11 |
12 | NodeMessageQueue = MessageQueue[NodeMessage]
13 |
14 | MessageSystem* = ref object of System
15 | messageQueue: NodeMessageQueue
16 |
17 | method onMessage*(c: ScriptComponent, id: MessageId, data: string, sender: Node) {.base.} = discard
18 |
19 | method init*(s: MessageSystem) =
20 | s.messageQueue = createMessageQueue[NodeMessage]()
21 |
22 | proc proceedMessage(s: MessageSystem, id: MessageId, msg: NodeMessage) =
23 | var sp = msg.path.split("/")
24 | var targetComponent = ""
25 | let lsp = sp[^1].split("#")
26 | if lsp.len > 1:
27 | sp[^1] = lsp[0]
28 | targetComponent = lsp[1]
29 |
30 | var receiver: Node
31 | try:
32 | receiver = msg.sender.findNode(sp)
33 | except Exception as e:
34 | warn "receiver not found ", $sp, " ", e.msg
35 |
36 | if receiver.isNil:
37 | warn "receiver not found ", $sp
38 | return
39 |
40 | if targetComponent.len == 0:
41 | warn "to target component"
42 | return
43 |
44 | let comp = receiver.componentIfAvailable(targetComponent)
45 | if comp.isNil or comp.isRenderComponent: return
46 | comp.ScriptComponent.onMessage(id, msg.data, msg.sender)
47 |
48 | method update*(s: MessageSystem, dt: float) =
49 | for id, msg in s.messageQueue.popChunk(chunk = 50):
50 | s.proceedMessage(id, msg)
51 |
52 | proc post(s: MessageSystem, id: string, msg: NodeMessage) =
53 | s.messageQueue.post(id, msg)
54 |
55 | proc post*(n: Node, path: string, id: string, data: string = "") =
56 | if n.sceneView.isNil:
57 | warn "Node sendMessage \"", id, "\" from node \"", n.name, "\" failed, sceneView is nil!"
58 | return
59 | var msg = NodeMessage(path: path, sender: n, data: data)
60 | n.sceneView.system(MessageSystem).post(id, msg)
61 |
62 | registerSystem(MessageSystem)
63 |
--------------------------------------------------------------------------------
/rod/systems.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ class_registry ]
2 | import rod / rod_types
3 | import tables
4 |
5 | export System
6 |
7 | method init*(s: System) {.base.} = discard
8 | method update*(s: System, dt: float) {.base.} = discard
9 | method draw*(s: System) {.base.} = discard
10 |
11 | proc createSystem*(name: string): System =
12 | if isClassRegistered(name) == false:
13 | raise newException(Exception, "System " & name & " is not registered")
14 |
15 | result = newObjectOfClass(name).System
16 | result.init()
17 |
18 | proc createSystem*(T: typedesc): System =
19 | result = createSystem($T)
20 |
21 | template registerSystem*(T: typedesc) =
22 | registerClass(T)
23 |
--------------------------------------------------------------------------------
/rod/tools/rodasset/migrator.nim:
--------------------------------------------------------------------------------
1 | import json, os, strutils
2 |
3 | import tree_traversal
4 |
5 | proc hasComponentOfType(n: JsonNode, typ: string): bool =
6 | for c in n.componentNodes:
7 | if c.typ == typ: return true
8 |
9 | proc convertDictComponentsToArray*(node: JsonNode) =
10 | let comps = node{"components"}
11 | if not comps.isNil and comps.kind == JObject:
12 | let newComps = newJArray()
13 | for k, v in comps:
14 | v["_c"] = %k
15 | newComps.add(v)
16 | node["components"] = newComps
17 |
18 | proc removeComponentsOfType(n: JsonNode, typ: string) =
19 | var i = 0
20 | let comps = n["components"]
21 | while i < comps.len:
22 | if comps[i]["_c"].str == typ:
23 | comps.elems.del(i)
24 | else:
25 | inc i
26 |
27 | proc removeAnimationsForProperties(composition: JsonNode, nodeName, propertyName: string) =
28 | let anims = composition{"animations"}
29 | let fqpn = nodeName & "." & propertyName
30 | if not anims.isNil:
31 | for animName, anim in anims:
32 | if fqpn in anim:
33 | echo "Deleting ", fqpn, " from animation ", animName
34 | anim.delete(fqpn)
35 |
36 | proc fixupChannelLevelsWithDrawablesOnTheSameNode(composition, n: JsonNode) =
37 | if n.hasComponentOfType("ChannelLevels") and (n.hasComponentOfType("Sprite") or n.hasComponentOfType("Text") or n.hasComponentOfType("Solid")):
38 | let children = n{"children"}
39 | if not children.isNil and children.len > 0:
40 | raise newException(Exception, "Found Channel levels in node with drawable and children")
41 | n.removeComponentsOfType("ChannelLevels")
42 | let name = n{"name"}
43 | if not name.isNil:
44 | echo "removing ChannelLevels from ", name
45 | for p in ["inWhite", "inBlack", "inGamma", "outWhite", "outBlack", "redInWhite",
46 | "redInBlack", "redInGamma", "redOutWhite", "redOutBlack", "greenInWhite",
47 | "greenInBlack", "greenInGamma", "greenOutWhite", "greenOutBlack", "blueInWhite",
48 | "blueInBlack", "blueInGamma", "blueOutWhite", "blueOutBlack"]:
49 |
50 | composition.removeAnimationsForProperties(name.str, p)
51 |
52 | proc upgradeTo1(composition: JsonNode): JsonNode =
53 | for n in composition.allNodes:
54 | n.convertDictComponentsToArray()
55 | fixupChannelLevelsWithDrawablesOnTheSameNode(composition, n)
56 |
57 | const upgraders = {
58 | 1: upgradeTo1
59 | }
60 |
61 | proc upgradeAssetBundle*(path: string) =
62 | for f in walkDirRec(path):
63 | if f.endsWith(".json") or f.endsWith(".jcomp"):
64 | echo "processing ", f
65 | try:
66 | var j = parseJson(readFile(f))
67 | for upgrader in upgraders:
68 | let v = j{"version"}.getInt()
69 | if v < upgrader[0]:
70 | let newV = upgrader[1](j)
71 | if not newV.isNil:
72 | j = newV
73 | j["version"] = %upgrader[0]
74 | writeFile(f, j.pretty().replace(" \l", "\l"))
75 | except:
76 | echo "ERROR: ", getCurrentExceptionMsg()
77 | echo getCurrentException().getStackTrace()
78 |
--------------------------------------------------------------------------------
/rod/tools/rodasset/rodasset.nim:
--------------------------------------------------------------------------------
1 | # This is a wrapper to rodasset_main. It compiles rodasset_main with rod
2 | # plugins of the project that calls the wrapper.
3 | import ../tool_wrapper
4 | runWrapper("rodasset", "rod/tools/rodasset/rodasset_main.nim")
5 |
--------------------------------------------------------------------------------
/rod/tools/rodasset/rodasset.nims:
--------------------------------------------------------------------------------
1 | --d:release
2 | --opt:speed
3 | --stackTrace:on
4 | --lineTrace:on
5 | --d:nimTrMacros
6 | --checks:on
7 | --threads:on
8 |
--------------------------------------------------------------------------------
/rod/tools/rodasset/settings.nim:
--------------------------------------------------------------------------------
1 | import hashes, strutils, os
2 |
3 | type AudioSettings* = object
4 | extension*: string # Audio file extension
5 |
6 | type GraphicsSettings* = object
7 | downsampleRatio*: float
8 | compressOutput*: bool
9 | compressToPVR*: bool
10 | extrusion*: int
11 | disablePotAdjustment*: bool
12 | packCompositions*: bool
13 | quantizeExceptions*: seq[string]
14 | posterizeExceptions*: seq[string]
15 | noDownsample*: seq[string]
16 | noAlphaCrop*: seq[string]
17 | useWebp*: bool
18 | webpQuality*: float
19 | webpLossless*: bool
20 |
21 | type Settings* = ref object
22 | graphics*: GraphicsSettings
23 | audio*: AudioSettings
24 | androidExternal*: bool
25 | debugOnly*: bool
26 |
27 | proc init(g: var GraphicsSettings) {.inline.} =
28 | g.downsampleRatio = 1.0
29 | g.compressOutput = true
30 | g.compressToPVR = false
31 | g.extrusion = 1
32 | g.disablePotAdjustment = true
33 | g.webpLossless = true
34 | g.quantizeExceptions = @[]
35 | g.posterizeExceptions = @[]
36 |
37 | proc newSettings*(): Settings =
38 | result.new()
39 | result.audio.extension = "ogg"
40 | result.graphics.init()
41 |
42 | proc hash*(s: AudioSettings | GraphicsSettings): Hash = hash($(s))
43 |
44 | proc parseExceptions(val, dir: string): seq[string] =
45 | let values = val.split(",")
46 |
47 | result = @[]
48 | for v in values:
49 | let vStripped = v.strip()
50 | if vStripped.contains("*"):
51 | for file in walkFiles(dir / vStripped):
52 | result.add(splitFile(file).name)
53 | else:
54 | result.add(vStripped)
55 |
56 | proc parseConfig*(rabFilePath: string, fast: bool = false): Settings =
57 | result = newSettings()
58 |
59 | result.graphics.extrusion = 1
60 | result.graphics.downsampleRatio = 1.0
61 | result.graphics.packCompositions = true
62 |
63 | for line in lines(rabFilePath):
64 | let pairs = line.split(" ", 1)
65 | doAssert(pairs.len == 2, "Wrong rab config params format in "& pairs[0])
66 | let key = pairs[0]
67 | let val = pairs[1]
68 | case key:
69 | of "extrusion":
70 | result.graphics.extrusion = parseInt(val)
71 | of "disablePotAdjustment":
72 | result.graphics.disablePotAdjustment = parseBool(val)
73 | of "downsampleRatio":
74 | result.graphics.downsampleRatio = parseFloat(val)
75 | of "exceptions":
76 | if not fast:
77 | result.graphics.quantizeExceptions = parseExceptions(val, rabFilePath.parentDir)
78 | of "noquant":
79 | if not fast:
80 | result.graphics.quantizeExceptions = parseExceptions(val, rabFilePath.parentDir)
81 | of "noposterize":
82 | if not fast:
83 | result.graphics.posterizeExceptions = parseExceptions(val, rabFilePath.parentDir)
84 | of "noDownsample":
85 | if not fast:
86 | result.graphics.noDownsample = parseExceptions(val, rabFilePath.parentDir)
87 | of "noAlphaCrop":
88 | if not fast:
89 | result.graphics.noAlphaCrop = parseExceptions(val, rabFilePath.parentDir)
90 | of "debugOnly":
91 | result.debugOnly = parseBool(val)
92 | of "packCompositions":
93 | result.graphics.packCompositions = parseBool(val)
94 | of "androidExternal":
95 | result.androidExternal = parseBool(val)
96 | of "quality":
97 | result.graphics.webpQuality = parseFloat(val)
98 | of "webpLossless":
99 | result.graphics.webpLossless = parseBool(val)
100 |
--------------------------------------------------------------------------------
/rod/tools/rodasset/tree_traversal.nim:
--------------------------------------------------------------------------------
1 | import json, lists
2 |
3 | proc append[T](lst: var DoublyLinkedList[T], elems: openarray[T]) =
4 | for e in elems: lst.append(e)
5 |
6 | iterator allNodes*(n: JsonNode): JsonNode =
7 | var lst = initDoublyLinkedList[JsonNode]() # SinglyLinkedList would be sufficient here, but its api is currently underdeveloped
8 | lst.append(n)
9 |
10 | while not lst.head.isNil:
11 | let n = lst.head.value
12 | lst.remove(lst.head)
13 | yield n
14 | let children = n{"children"}
15 | if not children.isNil:
16 | lst.append(children.elems)
17 |
18 | iterator componentNodes*(n: JsonNode): tuple[typ: string, node: JsonNode] =
19 | let components = n{"components"}
20 | if not components.isNil:
21 | var comps = newSeq[(string, JsonNode)]()
22 | if components.kind == JObject:
23 | for k, v in components:
24 | comps.add((k, v))
25 | elif components.kind == JArray:
26 | for c in components.elems:
27 | comps.add((c["_c"].str, c))
28 | for c in comps:
29 | yield c
30 |
31 | iterator componentNodesOfType*(n: JsonNode, typ: string): JsonNode =
32 | for c in n.componentNodes:
33 | if c.typ == typ: yield c.node
34 |
35 | iterator allComponentNodesOfType*(n: JsonNode, typ: string): (JsonNode, JsonNode) =
36 | for n in n.allNodes:
37 | for c in n.componentNodesOfType(typ):
38 | yield(n, c)
39 |
40 | iterator allSpriteNodes*(n: JsonNode): (JsonNode, JsonNode) =
41 | for n, c in allComponentNodesOfType(n, "Sprite"): yield(n, c)
42 | for n, c in allComponentNodesOfType(n, "NinePartSprite"): yield(n, c)
43 |
44 | iterator tilemapImageLayersNodes*(n: JsonNode): (JsonNode, JsonNode) =
45 | for n, c in allComponentNodesOfType(n, "ImageMapLayer"): yield(n, c)
46 |
47 | iterator tilemapNodes*(n: JsonNode): (JsonNode, JsonNode) =
48 | for n, c in allComponentNodesOfType(n, "TileMap"): yield(n, c)
49 |
50 |
51 | iterator allMeshComponentNodes*(n: JsonNode): (JsonNode, JsonNode) =
52 | for n, c in allComponentNodesOfType(n, "MeshComponent"): yield(n, c)
53 |
54 | iterator compositionAnimationsForNodeProperty*(compositionNode: JsonNode, nodeName, propertyName: string): (string, JsonNode) =
55 | let animName = nodeName & "." & propertyName
56 | for k, v in compositionNode["animations"]:
57 | let a = v{animName}
58 | if not a.isNil:
59 | yield(k, a)
60 |
61 | proc findNode*(n: JsonNode, name: string): JsonNode =
62 | for c in n.allNodes:
63 | if c{"name"}.getStr() == name: return c
64 |
65 | proc firstComponentOfType*(n: JsonNode, typ: string): JsonNode =
66 | for c in n.componentNodesOfType(typ): return c
67 |
--------------------------------------------------------------------------------
/rod/tools/tool_wrapper.nim:
--------------------------------------------------------------------------------
1 | import os, osproc, strutils
2 |
3 | proc rodPluginFile(): string =
4 | result = getCurrentDir() / "rodplugin.nim"
5 | if fileExists(result): return
6 | result = ""
7 |
8 | proc nimblePath(package: string): string =
9 | var (packageDir, err) = execCmdEx("nimble path " & package)
10 | if err == 0:
11 | let lines = packageDir.splitLines()
12 | if lines.len > 1:
13 | result = lines[^2]
14 |
15 | if result.len == 0:
16 | raise newException(Exception, "Package " & package & " not found in nimble packages")
17 |
18 | proc compileRealBin(bin, toolName, mainNim: string, useDanger: bool, cflags: seq[string]) =
19 | createDir(bin.parentDir())
20 | var args = @["c", "--threads:on", "-d:release",
21 | "-d:rodplugin", "--warning[LockLevel]:off"]
22 | if useDanger:
23 | args.add(@["-d:danger" ])
24 | else:
25 | args.add(@["--stackTrace:on", "--lineTrace:on"])
26 |
27 | args.add(cflags)
28 | args.add("--out:" & bin)
29 | let plug = rodPluginFile()
30 | if plug.len != 0:
31 | args.add("-d:rodPluginFile=" & plug)
32 | args.add("--path:" & plug.parentDir / "src") # TODO: "src" should be gone
33 | args.add(nimblePath("rod") / mainNim)
34 | let nim = findExe("nim")
35 | echo nim, " ", args.join(" ")
36 | if startProcess(nim, args = args, options = {poParentStreams}).waitForExit != 0:
37 | raise newException(Exception, toolName & " compilation failed")
38 |
39 | proc wrapperAUX(bin, toolName, pathToToolMainNim: string, useDanger:bool, cflags: seq[string] = @[]) =
40 | var needsCompile = not fileExists(bin)
41 | var passArgs = newSeq[string]()
42 |
43 | for i in 1 .. paramCount():
44 | let p = paramStr(i)
45 | if p == "--recompile":
46 | needsCompile = true
47 | else:
48 | passArgs.add(p)
49 |
50 | if needsCompile:
51 | echo "Compiling ", toolName
52 | compileRealBin(bin, toolName, pathToToolMainNim, useDanger, cflags)
53 |
54 | # Run the tool
55 | if startProcess(bin, args = passArgs, options = {poParentStreams}).waitForExit != 0:
56 | raise newException(Exception, toolName & " failed")
57 |
58 |
59 | proc runWrapper*(toolName, pathToToolMainNim: string) =
60 | var prefix = getEnv("ROD_ASSET_BIN")
61 | if prefix.len == 0:
62 | prefix = getTempDir()
63 | let cd = getCurrentDir()
64 | let projName = splitPath(cd).tail
65 | let bin = prefix / projName & "_" & toolName & (when defined(windows): ".exe" else: "")
66 | wrapperAUX(bin, toolName, pathToToolMainNim, useDanger = true)
67 |
68 | proc runEditorWrapper*(toolName, pathToToolMainNim: string) =
69 | let cd = getCurrentDir()
70 | let bin = cd / splitPath(cd).tail & "_" & toolName & (when defined(windows): ".exe" else: "")
71 | wrapperAUX(bin, toolName, pathToToolMainNim, useDanger = false, @["-d:rodedit"])
72 |
--------------------------------------------------------------------------------
/rod/utils/bin_serializer.nim:
--------------------------------------------------------------------------------
1 | import tables, streams, json, os, strutils
2 | import nimx / [image, types, pathutils ]
3 | import rod/quaternion
4 | import serialization_helpers
5 |
6 | when not defined(js):
7 | import os
8 |
9 | type
10 | BinSerializer* = ref object
11 | strTab*: Table[int16, string]
12 | revStrTab*: Table[string, int16]
13 | compsTable*: Table[string, int32]
14 | stream*: (when defined(js): Stream else: StringStream)
15 | stringEntries*: seq[int32]
16 | images*: JsonNode
17 | totalAlignBytes*: int
18 | assetBundlePath*: string
19 |
20 | proc newBinSerializer*(): BinSerializer =
21 | result.new()
22 |
23 | proc align*(b: BinSerializer, sz: int) =
24 | doAssert(sz <= 4)
25 | let p = b.stream.getPosition()
26 | let m = p mod sz
27 | if m != 0:
28 | inc(b.totalAlignBytes, sz - m)
29 | for i in 0 ..< (sz - m):
30 | b.stream.write(0xff'u8)
31 |
32 | proc newString*(b: BinSerializer, s: string): int16 =
33 | doAssert(not s.endsWith(".json"))
34 | if s in b.revStrTab:
35 | result = b.revStrTab[s]
36 | else:
37 | result = int16(b.strTab.len)
38 | b.strTab[result] = s
39 | b.revStrTab[s] = result
40 |
41 |
42 | type Serializable =
43 | array | openarray | tuple | object | seq | string | int8 | int16 | int32 | bool | enum | uint8 | uint16 | uint32 | Image
44 |
45 | proc write*(b: BinSerializer, data: float32) =
46 | b.align(sizeof(data))
47 | b.stream.write(data)
48 |
49 | proc write*[T: Serializable](b: BinSerializer, data: T)
50 |
51 | proc writeArrayNoLen*[T](b: BinSerializer, data: openarray[T]) =
52 | when isPODType(T):
53 | if data.len != 0:
54 | b.align(alignsize(type(data[0])))
55 | b.stream.writeData(unsafeAddr data[0], data.len * sizeof(data[0]))
56 | else:
57 | for i in 0 ..< data.len:
58 | b.write(data[i])
59 |
60 | proc writeStrNoLen*(b: BinSerializer, data: string) =
61 | if data.len != 0:
62 | b.stream.writeData(unsafeAddr data[0], data.len)
63 |
64 | proc getNeighbourImageBundlePath(b: BinSerializer, p2: string):tuple[asset:string, bundle:string]=
65 | when not defined(js):
66 | var curDir = getCurrentDir() / "res"
67 | var path = curDir / b.assetBundlePath / p2
68 |
69 | normalizePath(path, false)
70 | var dir = path.splitFile().dir
71 |
72 | for p in parentDirs(dir):
73 | if p == curDir: break
74 |
75 | if fileExists(p / "config.rab"):
76 | var p3 = path.substr(p.len + 1)
77 | path = p.subStr(curDir.len + 1)
78 | normalizePath(path, false)
79 | normalizePath(p3, false)
80 |
81 | return (asset:p3, bundle: path)
82 |
83 | raise newException(Exception, "Neighbour assetbundle not found for " & p2 )
84 |
85 | proc write*[T: Serializable](b: BinSerializer, data: T) =
86 | when T is array:
87 | b.writeArrayNoLen(data)
88 |
89 | elif T is tuple|object:
90 | when isPODType(T):
91 | b.align(alignsize(type(data[0])))
92 | b.stream.write(data)
93 | else:
94 | for k, v in fieldPairs(data):
95 | b.write(v)
96 |
97 | elif T is seq or T is openarray:
98 | doAssert(data.len <= uint16.high.int)
99 | b.write(data.len.uint16)
100 | if data.len != 0:
101 | b.writeArrayNoLen(data)
102 |
103 | elif T is string:
104 | if data.len == 0:
105 | b.write(int16(-1))
106 | else:
107 | b.align(sizeof(int16))
108 | b.stringEntries.add(b.stream.getPosition().int32)
109 | let off = b.newString(data)
110 | b.write(off)
111 |
112 | elif T is int16 | int32 | uint16 | uint32:
113 | b.align(sizeof(data))
114 | b.stream.write(data)
115 |
116 | elif T is int8 | uint8:
117 | b.stream.write(data)
118 |
119 | elif T is bool:
120 | b.stream.write(data.uint8)
121 |
122 | elif T is enum:
123 | var v = (when ord(high(T)) < high(int8): int8 else: int16)data
124 | b.write(v)
125 |
126 | elif T is Image:
127 | if data.isNil:
128 | b.write(int16(-1))
129 | else:
130 | var idx = 0
131 | let path = filePath(data)
132 |
133 | for j in b.images:
134 | if j["orig"].str == path:
135 | break
136 | inc idx
137 |
138 | if idx == b.images.len:
139 | b.write(int16(-2))
140 | let (asset, bundle) = b.getNeighbourImageBundlePath(path)
141 | b.write(asset)
142 | b.write(bundle)
143 | else:
144 | b.write(idx.int16)
145 |
146 | elif T is int | int64:
147 | {.error: "int and int64 not supported " .}
148 | else:
149 | {.error: "Unknown type " .}
150 |
151 | proc visit*(b: BinSerializer, v: float32) {.inline.} =
152 | b.write(v)
153 |
154 | proc visit*[T: Serializable](b: BinSerializer, v: T) {.inline.} =
155 | b.write(v)
156 |
157 | proc visit*(b: BinSerializer, v: Quaternion) =
158 | b.write(v.x)
159 | b.write(v.y)
160 | b.write(v.z)
161 | b.write(v.w)
162 |
163 | proc visit*(b: BinSerializer, images: seq[Image], frameOffsets: seq[Point]) =
164 | let sz = images.len
165 | b.write(sz.int16)
166 | for i in 0 ..< sz:
167 | b.write(images[i])
168 |
--------------------------------------------------------------------------------
/rod/utils/image_serialization.nim:
--------------------------------------------------------------------------------
1 | {.deprecated.}
2 | import rod/tools/serializer
3 | export deserializeImage
4 |
--------------------------------------------------------------------------------
/rod/utils/json_deserializer.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ image, types, pathutils, assets/asset_manager ]
2 | import rod/quaternion
3 | import json, strutils, os, strutils
4 |
5 |
6 | type JsonDeserializer* = ref object
7 | node*: JsonNode
8 | disableAwake*: bool
9 | compPath*: string # Path relative to bundle root
10 | getImageForPath*: proc(path: string, offset: var Point): Image
11 |
12 | proc newJsonDeserializer*(): JsonDeserializer =
13 | result.new()
14 |
15 | proc deserializeImage(b: JsonDeserializer, j: JsonNode): Image
16 |
17 | proc get*[T](b: JsonDeserializer, j: JsonNode, v: var T) =
18 | when T is float | float32 | float64:
19 | v = j.getFloat()
20 | elif T is int | int32 | int64 | int16 | uint64 | uint32 | uint16:
21 | v = T(j.getBiggestInt())
22 | elif T is string:
23 | v = j.getStr()
24 | elif T is bool:
25 | v = j.getBool()
26 | elif T is Rect:
27 | v = newRect(j[0].getFloat(), j[1].getFloat(), j[2].getFloat(), j[3].getFloat())
28 | elif T is Quaternion:
29 | v = newQuaternion(j[0].getFloat(), j[1].getFloat(), j[2].getFloat(), j[3].getFloat())
30 | elif T is Color:
31 | v = newColor(j[0].getFloat(), j[1].getFloat(), j[2].getFloat())
32 | if j.len > 3:
33 | v.a = j[3].getFloat()
34 | elif T is tuple:
35 | var i = 0
36 | for k, vv in fieldPairs(v):
37 | b.get(j[i], vv)
38 | inc i
39 | elif T is object:
40 | for k, vv in fieldPairs(v):
41 | b.get(j[k], vv)
42 | elif T is Image:
43 | v = b.deserializeImage(j)
44 |
45 | elif T is array:
46 | for i in 0 ..< v.len:
47 | b.get(j[i], v[i])
48 |
49 | elif T is enum:
50 | case j.kind
51 | of JString:
52 | v = parseEnum[T](j.str)
53 | of JInt:
54 | v = cast[T](j.num)
55 | else:
56 | discard
57 |
58 | elif T is seq:
59 | let sz = j.len
60 | v.setLen(sz)
61 | for i in 0 ..< sz:
62 | b.get(j[i], v[i])
63 | else:
64 | {.error: "unknown type " & $T .}
65 |
66 | proc imagePath(b: JsonDeserializer, jimage: JsonNode): string =
67 | case jimage.kind
68 | of JString:
69 | if jimage.str.len == 0:
70 | result = ""
71 | else:
72 | let parentDir = b.compPath.parentDirEx
73 | if parentDir.len > 0:
74 | result = parentDir & "/" & jimage.str
75 | else:
76 | result = jimage.str
77 | normalizePath(result, false)
78 | of JObject:
79 | result = jimage["orig"].getStr()
80 | else: doAssert(false)
81 |
82 | proc deserializeImage(b: JsonDeserializer, j: JsonNode, offset: var Point): Image =
83 | let path = b.imagePath(j)
84 | b.getImageForPath(path, offset)
85 |
86 | proc deserializeImage(b: JsonDeserializer, j: JsonNode): Image =
87 | var path = b.imagePath(j)
88 | if path.len == 0:
89 | return nil
90 |
91 | when not defined(rodplugin):
92 | var p = newPoint(0,0)
93 | result = b.getImageForPath(path, p)
94 |
95 | if result.isNil:
96 | result = imageWithSize(newSize(1.0, 1.0))
97 | result.setFilePath(path)
98 |
99 | proc visit*[T](b: JsonDeserializer, v: var T, key: string) =
100 | let j = b.node{key}
101 | if not j.isNil:
102 | b.get(j, v)
103 |
104 | proc visit*[T](b: JsonDeserializer, v: var T, key: string, default: T) =
105 | let j = b.node{key}
106 | if not j.isNil:
107 | b.get(j, v)
108 | else:
109 | v = default
110 |
111 | proc visit*(b: JsonDeserializer, images: var seq[Image], imagesKey: string, frameOffsets: var seq[Point], frameOffsetsKey: string) =
112 | let jimages = b.node[imagesKey]
113 | let sz = jimages.len
114 | images.setLen(sz)
115 | frameOffsets.setLen(sz)
116 |
117 | for i in 0 ..< sz:
118 | images[i] = b.deserializeImage(jimages[i], frameOffsets[i])
119 |
--------------------------------------------------------------------------------
/rod/utils/json_serializer.nim:
--------------------------------------------------------------------------------
1 | import json, os, strutils
2 | import nimx / [ types, image, pathutils ]
3 |
4 | type JsonSerializer* = ref object
5 | node*: JsonNode
6 | url*: string
7 |
8 | proc newJsonSerializer*(): JsonSerializer =
9 | result.new()
10 | result.node = newJObject()
11 |
12 | proc getRelativeResourcePath(b: JsonSerializer, path: string): string =
13 | var resourcePath = path
14 | when not defined(js) and not defined(android) and not defined(ios):
15 | resourcePath = urlParentDir(b.url)
16 | resourcePath.removePrefix("file://")
17 |
18 | var fixedPath = path
19 | fixedPath.removePrefix("file://")
20 | result = relativePathToPath(resourcePath, fixedPath)
21 | echo "save path = ", resourcePath, " relative = ", result, " url ", b.url
22 |
23 | proc writePath(b: JsonSerializer, data: string): JsonNode =
24 | %b.getRelativeResourcePath(data)
25 |
26 | proc write[T](b: JsonSerializer, data: T): JsonNode =
27 | when T is Rect:
28 | result = %[data.x, data.y, data.width, data.height]
29 | elif T is tuple:
30 | result = newJArray()
31 | for k, v in fieldPairs(data):
32 | result.add(b.write(v))
33 | elif T is object:
34 | result = newJObject()
35 | for k, v in fieldPairs(data):
36 | result[k] = b.write(v)
37 | elif T is seq | openarray:
38 | result = newJArray()
39 | for v in data:
40 | result.add(b.write(v))
41 | elif T is Image:
42 | if data.isNil:
43 | result = %""
44 | else:
45 | result = b.writePath(filePath(data))
46 | else:
47 | result = %data
48 |
49 | proc visit*(b: JsonSerializer, v: Image, key: string) =
50 | if not v.isNil:
51 | b.node[key] = b.writePath(filePath(v))
52 |
53 | proc visit*[T](b: JsonSerializer, v: seq[T], key: string) =
54 | if v.len > 0:
55 | b.node[key] = b.write(v)
56 |
57 | proc visit*[T](b: JsonSerializer, v: T, key: string) =
58 | b.node[key] = b.write(v)
59 |
60 | proc visit*(b: JsonSerializer, images: seq[Image], imagesKey: string, frameOffsets: seq[Point], frameOffsetsKey: string) =
61 | let jImages = newJArray()
62 | let jOffs = newJArray()
63 | var haveNonZeroOffset = false
64 |
65 | for i in 0 ..< images.len:
66 | jImages.add(b.write(images[i]))
67 |
68 | for i in 0 ..< frameOffsets.len:
69 | if frameOffsets[i] != zeroPoint:
70 | haveNonZeroOffset = true
71 | jOffs.add(b.write(frameOffsets[i]))
72 |
73 | if jImages.len != 0:
74 | b.node[imagesKey] = jImages
75 | if haveNonZeroOffset:
76 | b.node[frameOffsetsKey] = jOffs
77 |
--------------------------------------------------------------------------------
/rod/utils/property_desc.nim:
--------------------------------------------------------------------------------
1 | import macros, tables
2 |
3 | type
4 | PropertyDesc* = tuple
5 | name: string
6 | attributes: Table[string, NimNode]
7 |
8 | var props {.compileTime.} = initTable[string, seq[PropertyDesc]]()
9 |
10 | proc hasAttr*(d: PropertyDesc, attr: string): bool =
11 | attr in d.attributes
12 |
13 | proc hasProperties*(t: typedesc): bool {.compileTime.} =
14 | result = $t in props
15 |
16 | iterator propertyDescs*(typdesc: NimNode): PropertyDesc =
17 | let k = $typdesc
18 | if k in props:
19 | for p in props[k]:
20 | yield p
21 |
22 | proc addPropertyAttr(d: var PropertyDesc, a: NimNode) =
23 | case a.kind
24 | of nnkIdent:
25 | d.attributes[$a] = nil
26 | of nnkExprEqExpr:
27 | a[0].expectKind(nnkIdent)
28 | d.attributes[$a[0]] = a[1]
29 | of nnkStmtList:
30 | for c in a: addPropertyAttr(d, c)
31 | of nnkCall:
32 | a[0].expectKind(nnkIdent)
33 | assert(a.len == 2)
34 | var b = a[1]
35 | if b.kind == nnkStmtList:
36 | assert(b.len == 1)
37 | b = b[0]
38 | d.attributes[$a[0]] = b
39 | of nnkProcDef:
40 | d.attributes[$a.name] = a
41 | else:
42 | echo "Unexpected attr kind: ", treeRepr(a)
43 | assert(false)
44 |
45 | proc parsePropertyDescs(properties: NimNode): seq[PropertyDesc] =
46 | result = @[]
47 | for p in properties:
48 | case p.kind
49 | of nnkStmtList:
50 | for c in p: result.add(parsePropertyDescs(c))
51 | of nnkIdent:
52 | var pd: PropertyDesc
53 | pd.name = $p
54 | pd.attributes = initTable[string, NimNode]()
55 | result.add(pd)
56 | of nnkCall:
57 | var pd: PropertyDesc
58 | pd.name = $p[0]
59 | pd.attributes = initTable[string, NimNode]()
60 | for i in 1 ..< p.len:
61 | addPropertyAttr(pd, p[i])
62 | result.add(pd)
63 | of nnkDiscardStmt:
64 | discard
65 | else:
66 | echo "Unexpected property desc kind: ", treeRepr(p)
67 | assert(false)
68 |
69 | proc verifyProperties(pr: seq[PropertyDesc]) =
70 | for p in pr:
71 | for a in keys(p.attributes):
72 | assert(a in ["serializationKey", "noserialize", "phantom", "combinedWith", "default"], "Invalid attribute: " & a)
73 |
74 | macro properties*(typdesc: typed{nkSym}, body: untyped): untyped =
75 | let k = $typdesc
76 | # if k notin props:
77 | # props[k] = @[]
78 | assert(k notin props)
79 | props[k] = parsePropertyDescs(body)
80 | verifyProperties(props[k])
81 |
--------------------------------------------------------------------------------
/rod/utils/serialization_codegen.nim:
--------------------------------------------------------------------------------
1 | import nimx/class_registry
2 | import macros, tables
3 | import property_desc
4 |
5 | proc genPhantomTypeSection(typdesc: NimNode): NimNode =
6 | let fields = newNimNode(nnkRecList)
7 | for p in typdesc.propertyDescs:
8 | if p.hasAttr("phantom"):
9 | fields.add(newIdentDefs(newIdentNode(p.name), p.attributes["phantom"]))
10 | if fields.len > 0:
11 | result = newNimNode(nnkTypeSection).add(newNimNode(nnkTypeDef).add(newIdentNode("Phantom"), newEmptyNode(),
12 | newNimNode(nnkObjectTy).add(newEmptyNode(), newEmptyNode(), fields)))
13 |
14 | iterator serializablePropertyDescs(typdesc: NimNode): PropertyDesc =
15 | for p in typdesc.propertyDescs:
16 | if not p.hasAttr("noserialize"):
17 | yield p
18 |
19 | proc serializationKey(p: PropertyDesc): NimNode =
20 | if p.hasAttr("serializationKey"):
21 | result = copyNimTree(p.attributes["serializationKey"])
22 | else:
23 | result = newLit(p.name)
24 |
25 | proc actualReference(p: PropertyDesc, v: NimNode): NimNode =
26 | let o = if p.hasAttr("phantom"): newIdentNode("phantom") else: v
27 | newNimNode(nnkDotExpr).add(o, newIdentNode(p.name))
28 |
29 | proc propertyDescWithName(typdesc: NimNode, name: string): PropertyDesc =
30 | for p in typdesc.propertyDescs:
31 | if p.name == name: return p
32 |
33 | # TODO: The following is a hack. Instead we should get property descs from the typdesc itself
34 | result.name = name
35 | result.attributes = initTable[string, NimNode]()
36 |
37 | macro genSerializerProc(typdesc: typed{nkSym}, serTyp: typed{nkSym}, v: typed, s: typed,
38 | keyed: static[bool], serialize: static[bool],
39 | bin: static[bool], skipPhantom: static[bool]): untyped =
40 | let phantomIdent = newIdentNode("phantom")
41 |
42 | let phantomTyp = genPhantomTypeSection(typdesc)
43 |
44 | var paramTyp = typdesc
45 |
46 | let impl = getImpl(typdesc)
47 |
48 | # echo treeRepr(getImpl(typdesc.symbol))
49 |
50 | if not serialize and impl.kind == nnkTypeDef and impl.len >= 3 and impl[2].kind != nnkRefTy:
51 | paramTyp = newNimNode(nnkVarTy).add(paramTyp)
52 |
53 | result = newNimNode(nnkStmtList)
54 |
55 | if not phantomTyp.isNil and phantomTyp.kind != nnkEmpty:
56 | result.add(phantomTyp)
57 | let pv = quote do:
58 | var `phantomIdent`: Phantom
59 | result.add(pv)
60 |
61 | if serialize and not skipPhantom:
62 | result.add(newCall("toPhantom", v, phantomIdent))
63 |
64 | for p in typdesc.serializablePropertyDescs:
65 | let visitCall = newCall(ident("visit"), s, actualReference(p, v))
66 | if keyed: visitCall.add(p.serializationKey())
67 |
68 | if p.hasAttr("combinedWith"):
69 | let p1 = typdesc.propertyDescWithName($p.attributes["combinedWith"])
70 | visitCall.add(actualReference(p1, v))
71 | if keyed: visitCall.add(p1.serializationKey())
72 |
73 | if keyed and not serialize and not bin and p.hasAttr("default"):
74 | visitCall.add(p.attributes["default"])
75 |
76 | result.add(visitCall)
77 | # let echoPrefix = newLit($serTyp & " " & p.name & ": ")
78 | # let echoValue = actualReference(p, v)
79 | # result.add quote do:
80 | # when compiles(echo(`echoPrefix`, `echoValue`)):
81 | # echo `echoPrefix`, `echoValue`
82 | # else:
83 | # echo `echoPrefix`, "..."
84 |
85 | if not phantomTyp.isNil and phantomTyp.kind != nnkEmpty:
86 | if not serialize and not skipPhantom:
87 | result.add(newCall("fromPhantom", v, phantomIdent))
88 |
89 | if not serialize:
90 | result.add quote do:
91 | when compiles(awake(`v`)):
92 | if not `s`.disableAwake:
93 | awake(`v`)
94 |
95 | # echo repr(result)
96 |
97 | template genSerializationCodeForComponent*(c: typed) =
98 | import rod / utils / [ bin_deserializer ]
99 |
100 | when defined(rodplugin):
101 | import rod / utils / [ json_deserializer, json_serializer,
102 | bin_serializer, serialization_hash_calculator ]
103 |
104 | bind className
105 | method deserialize*(v: c, b: JsonDeserializer) =
106 | when hasProperties(superType(c)):
107 | procCall deserialize(superType(c)(v), b)
108 |
109 | genSerializerProc(c, JsonDeserializer, v, b, true, false, false, false)
110 |
111 | method serialize*(v: c, b: JsonSerializer) =
112 | when hasProperties(superType(c)):
113 | procCall serialize(superType(c)(v), b)
114 |
115 | b.visit(className(v), "_c")
116 | genSerializerProc(c, JsonSerializer, v, b, true, true, false, false)
117 |
118 | method serialize*(v: c, b: BinSerializer) =
119 | when hasProperties(superType(c)):
120 | procCall serialize(superType(c)(v), b)
121 |
122 | genSerializerProc(c, BinSerializer, v, b, false, true, true, false)
123 |
124 | method serializationHash*(v: c, b: SerializationHashCalculator) =
125 | when hasProperties(superType(c)):
126 | procCall serializationHash(superType(c)(v), b)
127 |
128 | genSerializerProc(c, SerializationHashCalculator, v, b, true, true, false, true)
129 |
130 | method deserialize*(v: c, b: BinDeserializer) =
131 | when hasProperties(superType(c)):
132 | procCall deserialize(superType(c)(v), b)
133 |
134 | genSerializerProc(c, BinDeserializer, v, b, false, false, true, false)
135 |
136 | template genJsonSerializationrFor*(c: typed) =
137 | import rod / utils / [ json_deserializer, json_serializer ]
138 |
139 | proc toJson*(v: c): JsonNode=
140 | var b = newJsonSerializer()
141 | genSerializerProc(c, JsonSerializer, v, b, true, true, false, false)
142 | result = b.node
143 |
144 | proc `to c`*(jn: JsonNode): c =
145 | var b = newJsonDeserializer()
146 | b.node = jn
147 | when c is ref:
148 | result.new()
149 | genSerializerProc(c, JsonDeserializer, result, b, true, false, false, false)
150 |
--------------------------------------------------------------------------------
/rod/utils/serialization_hash_calculator.nim:
--------------------------------------------------------------------------------
1 | import nimx / [ types, image ]
2 | import rod/quaternion
3 | import hashes
4 |
5 | type SerializationHashCalculator* = ref object
6 | hash*: Hash
7 |
8 | proc newSerializationHashCalculator*(): SerializationHashCalculator =
9 | result.new()
10 |
11 | type ValueType = enum
12 | tyFloat32
13 | tyInt16
14 | tyInt32
15 | tyEnum
16 | tyBool
17 | tyString
18 | tyImage
19 | tySeq
20 | tyArray
21 | tyTuple
22 | tyQuaternion
23 |
24 | proc mix(b: SerializationHashCalculator, t: int) =
25 | b.hash = b.hash !& t
26 |
27 | proc mix(b: SerializationHashCalculator, t: ValueType) {.inline.} =
28 | b.mix(int(t))
29 |
30 | proc mix(b: SerializationHashCalculator, k: string) =
31 | b.hash = b.hash !& hash(k)
32 |
33 | proc mixValueType(b: SerializationHashCalculator, T: typedesc) =
34 | when T is int16:
35 | b.mix(tyInt16)
36 | elif T is int32:
37 | b.mix(tyInt32)
38 | elif T is float32:
39 | b.mix(tyFloat32)
40 | elif T is bool:
41 | b.mix(tyBool)
42 | elif T is string:
43 | b.mix(tyString)
44 | elif T is Image:
45 | b.mix(tyImage)
46 | elif T is seq:
47 | b.mix(tySeq)
48 | var v: T
49 | b.mixValueType(type(v[0]))
50 | elif T is array:
51 | b.mix(tyArray)
52 | var v: T
53 | b.mixValueType(type(v[0]))
54 | b.mix(v.len)
55 | elif T is enum:
56 | b.mix(tyEnum)
57 | b.mix(sizeof(T))
58 | elif T is tuple:
59 | b.mix(tyTuple)
60 | var v: T
61 | for k, vv in fieldPairs(v):
62 | b.mixValueType(type(vv))
63 |
64 | proc mix[T](b: SerializationHashCalculator, v: T, k: string) {.inline.} =
65 | b.mixValueType(T)
66 | b.mix(k)
67 |
68 | proc visit*(b: SerializationHashCalculator, images: seq[Image], imagesKey: string, frameOffsets: seq[Point], frameOffsetsKey: string) =
69 | b.mix(tyImage, imagesKey)
70 | b.mix(tySeq, frameOffsetsKey)
71 | b.mix(111)
72 |
73 | proc visit*(b: SerializationHashCalculator, v: Quaternion, key: string) =
74 | b.mix(tyQuaternion, key)
75 |
76 | proc visit*[T](b: SerializationHashCalculator, v: T, key: string) =
77 | b.mix(v, key)
78 |
--------------------------------------------------------------------------------
/rod/utils/serialization_helpers.nim:
--------------------------------------------------------------------------------
1 |
2 | proc arrayElementType[T](a: typedesc[openarray[T]]): T = discard
3 |
4 | proc isPODType*(T: typedesc): bool {.compileTime.} =
5 | when T is float32 | int16 | int8 | int32:
6 | return true
7 | elif T is array:
8 | var dummy: T
9 | return isPODType(type(dummy[0]))
10 | elif T is openarray:
11 | return isPODType(type(arrayElementType(T)))
12 | elif T is tuple:
13 | var v: T
14 | for k, vv in fieldPairs(v):
15 | if not isPODType(type(vv)): return false
16 | return true
17 | else:
18 | return false
19 |
20 | var a : tuple[a: int32, b: float32]
21 | when isPODType(type(a)):
22 | discard
23 | else:
24 | {.error: "no POD".}
25 |
26 |
27 | template alignsize*(t: typedesc): int =
28 | if sizeof(t) > 4:
29 | 4
30 | else:
31 | sizeof(t)
32 |
33 | # const testa:tuple[a: int32, b: float32]
34 |
35 | # static:
36 | # doAssert(isPODType(int32))
37 | # doAssert(isPODType(testa))
38 | # doAssert(not isPODType(tuple[a: int32, b: string]))
39 |
--------------------------------------------------------------------------------
/rod/utils/text_helpers.nim:
--------------------------------------------------------------------------------
1 | import nimx/types
2 | import strutils
3 |
4 | proc fromHexColor*(clr: string): Color =
5 | doAssert(clr.len == 8)
6 |
7 | template getComp(start: int): float =
8 | parseHexInt(clr.substr(start, start + 1)).float / 255.0
9 |
10 | result.r = getComp(0)
11 | result.g = getComp(2)
12 | result.b = getComp(4)
13 | result.a = getComp(6)
14 |
15 | proc removeTextAttributes*(text: string): string =
16 | var tagOpened: bool
17 | for i in 0..":
24 | tagOpened = false
--------------------------------------------------------------------------------
/rod/vertex_data_info.nim:
--------------------------------------------------------------------------------
1 | import nimx/portable_gl
2 |
3 | type VertexDataInfo* = object
4 | numOfCoordPerVert*: GLint
5 | numOfCoordPerTexCoord*: GLint
6 | numOfCoordPerNormal*: GLint
7 | numOfCoordPerTangent*: GLint
8 | numOfCoordPerBinormal*: GLint
9 | stride*: int
10 |
11 | proc newVertexInfoWithVertexData*(vertexDataLen = 0, texCoordDataLen = 0, normalDataLen = 0, tangentDataLen = 0, binormalDataLen: int = 0): VertexDataInfo =
12 | if vertexDataLen != 0:
13 | result.numOfCoordPerVert = 3
14 | if texCoordDataLen != 0:
15 | result.numOfCoordPerTexCoord = 2
16 | if normalDataLen != 0:
17 | result.numOfCoordPerNormal = 3
18 | if binormalDataLen != 0:
19 | result.numOfCoordPerBinormal = 3
20 | if tangentDataLen != 0:
21 | result.numOfCoordPerTangent = 3
22 | result.stride = (result.numOfCoordPerVert +
23 | result.numOfCoordPerTexCoord +
24 | result.numOfCoordPerNormal +
25 | result.numOfCoordPerBinormal +
26 | result.numOfCoordPerTangent) * sizeof(GLfloat)
27 |
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | .rodedit/
2 | build/
3 | *.exe
4 |
--------------------------------------------------------------------------------
/template/main.nim:
--------------------------------------------------------------------------------
1 | import nimx/[view, app, scroll_view, table_view, text_field, autotest, window]
2 | import src/game/example_scene
3 |
4 | const isMobile = defined(ios) or defined(android)
5 |
6 | let wndRect = newRect(40, 40, 1280, 720)
7 | proc startApplication() =
8 | var mainWindow : Window
9 |
10 | when isMobile:
11 | mainWindow = newFullscreenWindow()
12 | else:
13 | mainWindow = newWindow(wndRect)
14 |
15 | mainWindow.title = "Template"
16 |
17 | let gs = new(ExampleScene)
18 | gs.init(mainWindow.bounds)
19 | gs.autoresizingMask = { afFlexibleWidth, afFlexibleHeight }
20 | mainWindow.addSubview(gs)
21 |
22 | gs.setFrame(mainWindow.bounds)
23 | gs.resizeSubviews(mainWindow.bounds.size)
24 |
25 | runApplication:
26 | startApplication()
27 |
--------------------------------------------------------------------------------
/template/nakefile.nim:
--------------------------------------------------------------------------------
1 | import nimx/naketools
2 | import osproc, os
3 |
4 |
5 | const additionalFonts: seq[string] = @[]
6 |
7 | proc rodasset(b: Builder, command: string, arguments: varargs[string]) =
8 | let downsampleRatio = 1
9 | var args = @["rodasset", command, "--platform=" & b.platform, "--downsampleRatio=" & $downsampleRatio]
10 | args.add(arguments)
11 | direShell(args)
12 |
13 |
14 | beforeBuild = proc(b: Builder) =
15 | b.mainFile = "main.nim"
16 | b.originalResourcePath = "res"
17 | # b.resourcePath = "build"
18 | b.additionalNimFlags.add(@["--path:res", "--path:src"])
19 |
20 |
21 | preprocessResources = proc(b: Builder) =
22 | if b.platform == "ios":
23 | copyDir(b.originalResourcePath / "ios", b.resourcePath)
24 |
25 | if b.platform == "ios" or b.platform == "ios-sim" or b.platform == "emscripten":
26 | b.copyResourceAsIs("OpenSans-Regular.ttf")
27 | for f in additionalFonts:
28 | b.copyResourceAsIs(f)
29 |
30 | var args = newSeq[string]()
31 | if b.debugMode:
32 | args.add("--debug")
33 | args.add(["--src=" & b.originalResourcePath, "--dst=" & b.resourcePath])
34 | b.rodasset("pack", args)
35 |
--------------------------------------------------------------------------------
/template/res/example_bundle/config.rab:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/template/res/example_bundle/config.rab
--------------------------------------------------------------------------------
/template/res/example_bundle/logo-crown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yglukhov/rod/3362ca3a6af0480e7af864b8db6e7fb079203f68/template/res/example_bundle/logo-crown.png
--------------------------------------------------------------------------------
/template/src/core/asset_loader.nim:
--------------------------------------------------------------------------------
1 | import nimx/assets/asset_manager
2 | from nimx/assets/abstract_asset_bundle import nil
3 | import nimx / [ notification_center, abstract_window ]
4 | import rod / asset_bundle
5 |
6 | import logging
7 | export asset_bundle
8 |
9 | type AssetsLoader* = object
10 | assets: seq[AssetBundleDescriptor]
11 |
12 | onRodAssetBundleDownloadingStart = proc(asset: string) =
13 | info "[AssetsLoader] Asset downloading started ", asset
14 |
15 | onRodAssetBundleDownloadingEnd = proc(asset: string, error: string) =
16 | info "[AssetsLoader] Asset downloading ended ", asset, " with error ", error.len > 0
17 |
18 | onRodAssetBundleDownloadingProgress = proc(asset: string, p: float) =
19 | info "[AssetsLoader] Asset downloading ", asset, " progress ", p
20 |
21 | proc load*(a: var AssetsLoader, assets: openarray[AssetBundleDescriptor], onLoadProgress: proc(p: float) {.gcsafe.} = nil, onLoaded: proc() {.gcsafe.} ) =
22 | a.assets = @assets
23 | assert(a.assets.len > 0, "[AssetsLoader] nothing to load")
24 | assets.loadAssetBundles() do(mountPaths: openarray[string], abs: openarray[AssetBundle], err: string):
25 | if err.len != 0:
26 | sharedNotificationCenter().postNotification("SHOW_RESOURCE_LOADING_ALERT")
27 | return
28 | let am = sharedAssetManager()
29 | for i, mnt in mountPaths:
30 | info "[AssetsLoader] mounting ", mnt
31 | am.mount(mnt, abs[i])
32 | var newAbs = newSeqOfCap[abstract_asset_bundle.AssetBundle](abs.len)
33 | for ab in abs: newAbs.add(ab)
34 | am.loadAssetsInBundles(newAbs, onLoadProgress) do():
35 | # info am.dump()
36 | if not onLoaded.isNil:
37 | onLoaded()
38 |
39 | proc free*(a: var AssetsLoader) =
40 | let am = sharedAssetManager()
41 | assert(a.assets.len > 0, "[AssetsLoader] nothing to unload")
42 | for ab in a.assets:
43 | info "[AssetsLoader] unmounting ", ab.path
44 | am.unmount(ab.path)
45 | a.assets.setLen(0)
46 | # info am.dump()
47 | requestGCFullCollect()
48 |
--------------------------------------------------------------------------------
/template/src/core/game_scene.nim:
--------------------------------------------------------------------------------
1 | import nimx/[view, types, button, animation, mini_profiler, matrixes, view_event_handling]
2 | import rod/[viewport, rod_types, node, component, component/ui_component, edit_view]
3 | import asset_loader
4 |
5 | export asset_loader, viewport
6 |
7 | const viewportSize = newSize(1920, 1080)
8 |
9 | type GameScene* = ref object of SceneView
10 | assetLoader: AssetsLoader
11 |
12 | proc centerOrthoCameraPosition*(gs: GameScene) =
13 | assert(not gs.camera.isNil, "GameSceneBase's camera is nil")
14 | let cameraNode = gs.camera.node
15 |
16 | cameraNode.positionX = viewportSize.width / 2
17 | cameraNode.positionY = viewportSize.height / 2
18 |
19 | proc addDefaultOrthoCamera*(gs: GameScene, cameraName: string) =
20 | let cameraNode = gs.rootNode.newChild(cameraName)
21 | let camera = cameraNode.component(Camera)
22 |
23 | camera.projectionMode = cpOrtho
24 | camera.viewportSize = viewportSize
25 | cameraNode.positionZ = 1
26 | gs.centerOrthoCameraPosition()
27 |
28 | method acceptsFirstResponder(v: GameScene): bool = true
29 |
30 | method onKeyDown*(gs: GameScene, e: var Event): bool =
31 | if e.keyCode == VirtualKey.E:
32 | ## start's editor
33 | discard startEditingNodeInView(gs.rootNode, gs)
34 | result = true
35 |
36 | method assetBundles*(gs: GameScene): seq[AssetBundleDescriptor] {.base.} = discard
37 | method onResourcesLoaded*(gs: GameScene) {.base.} = discard
38 |
39 | method init*(gs: GameScene, frame: Rect)=
40 | procCall gs.SceneView.init(frame)
41 | gs.rootNode = newNode("root")
42 | gs.addDefaultOrthoCamera("camera")
43 |
44 | proc afterResourcesPreloaded() =
45 | gs.onResourcesLoaded()
46 |
47 | let abd = gs.assetBundles()
48 | if abd.len > 0:
49 | gs.assetLoader.load(abd, onLoadProgress = nil, onLoaded = afterResourcesPreloaded)
50 | else:
51 | afterResourcesPreloaded()
52 |
53 | method viewOnExit*(gs: GameScene) =
54 | if gs.assetBundles().len > 0:
55 | gs.assetLoader.free()
56 |
57 |
--------------------------------------------------------------------------------
/template/src/game/example_scene.nim:
--------------------------------------------------------------------------------
1 | import rod / [ rod_types, node, component ]
2 | import nimx / animation
3 | import .. / core / game_scene
4 |
5 | type
6 | ExampleScene* = ref object of GameScene
7 |
8 | method assetBundles*(gs: ExampleScene): seq[AssetBundleDescriptor] =
9 | const assetBundles = @[
10 | assetBundleDescriptor("example_bundle")
11 | ]
12 | result = assetBundles
13 |
14 | method onResourcesLoaded*(gs: ExampleScene) =
15 | var comp = newNodeWithResource("example_bundle/composition2")
16 | var anim = comp.animationNamed("idle")
17 | anim.numberOfLoops = -1
18 | comp.addAnimation(anim)
19 | gs.rootNode.addChild(comp)
20 |
--------------------------------------------------------------------------------
/template/template.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 | version = "0.1.0"
3 | author = "Anonymous"
4 | description = "Rod project template"
5 | license = "MIT"
6 |
7 | requires "https://github.com/yglukhov/rod"
8 |
--------------------------------------------------------------------------------