diff --git a/codepad/bundle_resources/web/static/codepad.css b/codepad/bundle_resources/web/static/codepad.css
index 69a5cfe..0c21241 100644
--- a/codepad/bundle_resources/web/static/codepad.css
+++ b/codepad/bundle_resources/web/static/codepad.css
@@ -157,10 +157,18 @@ svg, .svg-image {
margin-right: 6px;
}
-#pane-editor, #pane-console {
+#pane-editor, #pane-console, #pane-canvas, #row-top, #row-bottom, #inspector-pane {
position: relative;
}
+#inspector-wrap {
+ position: absolute;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+}
+
#editor-wrap {
width: 100%;
position: absolute;
@@ -219,6 +227,121 @@ svg, .svg-image {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
}
+#hierarchy-pane, #properties-pane {
+ position: relative;
+ height: 100%;
+}
+
+#hierarchy-tree, #properties-list {
+ position: absolute;
+ top: 38px;
+ bottom: 8px;
+ left: 8px;
+ right: 8px;
+ overflow: auto;
+ font-size: 12px;
+}
+
+#hierarchy-pane .tabs-wrap, #properties-pane .tabs-wrap {
+ background-color: #2a2d32;
+ border-bottom: 1px solid #1f2125;
+}
+
+#hierarchy-pane .tabs-wrap label, #properties-pane .tabs-wrap label {
+ opacity: 1;
+ border-bottom: none;
+ color: #cfd3d7;
+ font-weight: 600;
+}
+
+.tree-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 6px;
+ border-radius: 2px;
+ cursor: pointer;
+}
+
+.tree-item:hover {
+ background-color: #34373c;
+}
+
+.tree-item.is-selected {
+ background-color: #3d4046;
+ color: #ffffff;
+}
+
+.tree-caret {
+ width: 10px;
+ height: 10px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: #9aa0a6;
+ font-size: 10px;
+ flex: 0 0 10px;
+}
+
+.tree-caret::before {
+ content: "\25B8";
+ transform: rotate(0deg);
+ transition: transform 0.15s ease;
+}
+
+.tree-node.is-expanded > .tree-item .tree-caret::before {
+ transform: rotate(90deg);
+}
+
+.tree-node.is-collapsed > .tree-children {
+ display: none;
+}
+
+.tree-label {
+ font-weight: 600;
+}
+
+.tree-meta {
+ margin-left: auto;
+ opacity: 0.6;
+ font-size: 11px;
+}
+
+.tree-children {
+ margin-left: 14px;
+ border-left: 1px solid #32353a;
+ padding-left: 6px;
+}
+
+.properties-header {
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #eff2f6;
+}
+
+.prop-row {
+ display: grid;
+ grid-template-columns: 120px 1fr;
+ gap: 8px;
+ padding: 6px 0px;
+}
+
+.prop-key {
+ color: #9aa0a6;
+ text-transform: capitalize;
+}
+
+.prop-value {
+ background-color: #25282d;
+ border: 1px solid #3a3d43;
+ border-radius: 3px;
+ color: #d0d4d9;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
+ white-space: pre-wrap;
+ word-break: break-word;
+ padding: 4px 6px;
+}
+
.gutter {
}
@@ -235,6 +358,11 @@ svg, .svg-image {
float: left;
}
+.split.split-vertical, .gutter.gutter-vertical {
+ width: 100%;
+ float: none;
+}
+
.canvas-app-container {
background: #000;
position: relative;
diff --git a/codepad/bundle_resources/web/static/codepad.js b/codepad/bundle_resources/web/static/codepad.js
index e22fb5f..928bf06 100644
--- a/codepad/bundle_resources/web/static/codepad.js
+++ b/codepad/bundle_resources/web/static/codepad.js
@@ -15,6 +15,14 @@ var scenes = [];
var project_info = {};
var engine_info = {};
+var scene_hierarchy = null;
+var scene_node_index = {};
+var scene_selected_path = null;
+var scene_structure_signature = null;
+var scene_dump_running = false;
+var scene_dump_frame = 0;
+var scene_dump_missing_warned = false;
+var scene_dump_filter = null;
var default_script = `function init(self)
@@ -87,14 +95,39 @@ function codepad_load_editor(callback) {
//editor.session.setMode("ace/mode/lua");
// Setup panel splitters
- Split(['#pane-editors', '#pane-canvas'], {
- direction: 'vertical',
- onDrag: function () { fix_canvas_size(); }
- });
+ if (document.getElementById("row-top") && document.getElementById("row-bottom")) {
+ Split(['#row-top', '#row-bottom'], {
+ direction: 'vertical',
+ sizes: [55, 45],
+ minSize: [160, 160],
+ onDrag: function () { fix_canvas_size(); }
+ });
+ }
- Split(['#pane-console', '#pane-editor'], {
- sizes: [30, 70]
- });
+ if (document.getElementById("pane-console") && document.getElementById("pane-editor")) {
+ Split(['#pane-console', '#pane-editor'], {
+ direction: 'horizontal',
+ sizes: [30, 70],
+ minSize: [180, 320]
+ });
+ }
+
+ if (document.getElementById("inspector-pane") && document.getElementById("pane-canvas")) {
+ Split(['#inspector-pane', '#pane-canvas'], {
+ direction: 'horizontal',
+ sizes: [30, 70],
+ minSize: [180, 320],
+ onDrag: function () { fix_canvas_size(); }
+ });
+ }
+
+ if (document.getElementById("hierarchy-pane") && document.getElementById("properties-pane")) {
+ Split(['#hierarchy-pane', '#properties-pane'], {
+ direction: 'vertical',
+ sizes: [50, 50],
+ minSize: [80, 80]
+ });
+ }
if (callback) {
callback();
@@ -194,6 +227,9 @@ function codepad_change_scene() {
break;
}
}
+ scene_structure_signature = null;
+ scene_selected_path = null;
+ codepad_set_dump_filter();
}
@@ -234,6 +270,10 @@ function codepad_ready(scenes_json, project_json, engine_json) {
codepad_trigger_url_check();
codepad_change_scene();
+ setTimeout(function () {
+ codepad_dump_hierarchy(true);
+ codepad_start_dump_loop();
+ }, 0);
}
/**
@@ -447,12 +487,23 @@ function codepad_is_embedded() {
function fix_canvas_size(event) {
var canvas = document.getElementById('canvas');
+ if (!canvas) {
+ return;
+ }
+ var container = document.getElementById("app-container") || canvas.parentElement;
+ var rect = container ? container.getBoundingClientRect() : canvas.getBoundingClientRect();
+ var width = rect.width;
+ var height = rect.height;
if (codepad_is_embedded()) {
- canvas.width = document.body.offsetWidth;
- canvas.height = document.body.offsetHeight;
- } else {
- canvas.width = canvas.offsetWidth;
- canvas.height = canvas.offsetHeight;
+ width = document.body.offsetWidth || width;
+ height = document.body.offsetHeight || height;
+ }
+ if (width > 0 && height > 0) {
+ var dpr = window.devicePixelRatio || 1;
+ canvas.style.width = Math.round(width) + "px";
+ canvas.style.height = Math.round(height) + "px";
+ canvas.width = Math.max(1, Math.round(width * dpr));
+ canvas.height = Math.max(1, Math.round(height * dpr));
}
}
@@ -471,8 +522,14 @@ function codepad_show_play_embed(callback) {
};
splash.innerHTML = "
Run code
";
document.body.classList += "embedded";
- var pane_editors = document.getElementById("pane-editors");
- pane_editors.remove();
+ var row_top = document.getElementById("row-top");
+ if (row_top) {
+ row_top.remove();
+ }
+ var inspector = document.getElementById("inspector-pane");
+ if (inspector) {
+ inspector.remove();
+ }
}
function codepad_start(callback) {
diff --git a/codepad/bundle_resources/web/static/outline.js b/codepad/bundle_resources/web/static/outline.js
new file mode 100644
index 0000000..1ac095b
--- /dev/null
+++ b/codepad/bundle_resources/web/static/outline.js
@@ -0,0 +1,405 @@
+/*jshint esversion: 6 */
+
+/**
+ * Fetch the scene hierarchy JSON from the native extension and update UI.
+ */
+function codepad_dump_hierarchy(silent) {
+ if (typeof Module === "undefined" || !Module.ccall) {
+ if (!scene_dump_missing_warned && !silent) {
+ console.warn("Scene dump unavailable: Module.ccall is missing.");
+ scene_dump_missing_warned = true;
+ }
+ return;
+ }
+ try {
+ codepad_set_dump_filter();
+ var ptr = Module.ccall("CodepadSceneDump_DumpJson", "number", [], []);
+ if (!ptr) {
+ if (!silent) {
+ console.warn("Scene dump returned no data.");
+ }
+ return;
+ }
+ var json = Module.UTF8ToString(ptr);
+ var data = JSON.parse(json);
+ if (Array.isArray(data)) {
+ data = { _synthetic: true, children: data, props: { id: codepad_get_scene() || "scene" } };
+ }
+ scene_hierarchy = data;
+ codepad_index_hierarchy(data);
+ var signature = codepad_build_structure_signature(data);
+ var structure_changed = signature !== scene_structure_signature;
+ scene_structure_signature = signature;
+ if (structure_changed || !document.getElementById("hierarchy-tree") || !document.getElementById("hierarchy-tree").hasChildNodes()) {
+ codepad_render_hierarchy(data);
+ } else {
+ codepad_render_properties(scene_node_index[scene_selected_path]);
+ }
+ if (!silent) {
+ console.log("Scene hierarchy:", data);
+ }
+ return data;
+ } catch (err) {
+ console.error("Scene dump failed:", err);
+ }
+}
+
+/**
+ * Update native dump filter to limit data to the active scene.
+ */
+function codepad_set_dump_filter() {
+ if (typeof Module === "undefined" || !Module.ccall) {
+ return;
+ }
+ var scene_id = codepad_get_scene();
+ if (!scene_id || scene_id === scene_dump_filter) {
+ return;
+ }
+ Module.ccall("CodepadSceneDump_SetFilter", null, ["string"], [scene_id]);
+ scene_dump_filter = scene_id;
+}
+
+/**
+ * Start the per-frame (every other frame) hierarchy polling loop.
+ */
+function codepad_start_dump_loop() {
+ if (scene_dump_running) {
+ return;
+ }
+ scene_dump_running = true;
+ function tick() {
+ if (!scene_dump_running) {
+ return;
+ }
+ scene_dump_frame += 1;
+ if (scene_dump_frame % 2 === 0) {
+ codepad_dump_hierarchy(true);
+ }
+ requestAnimationFrame(tick);
+ }
+ requestAnimationFrame(tick);
+}
+
+/**
+ * Build a signature string for detecting hierarchy structure changes.
+ */
+function codepad_build_structure_signature(node) {
+ var parts = [];
+ (function walk(current) {
+ if (!current) {
+ return;
+ }
+ if (current._synthetic) {
+ if (current.children) {
+ for (var i = 0; i < current.children.length; i++) {
+ walk(current.children[i]);
+ }
+ }
+ return;
+ }
+ parts.push(current._key || "");
+ parts.push((current.props && current.props.id) || current.id || current.name || "");
+ parts.push(current.type || "");
+ var count = current.children ? current.children.length : 0;
+ parts.push(String(count));
+ if (current.children) {
+ for (var i = 0; i < current.children.length; i++) {
+ walk(current.children[i]);
+ }
+ }
+ })(node);
+ return parts.join("|");
+}
+
+/**
+ * Escape a string for safe HTML insertion.
+ */
+function codepad_escape_html(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\"/g, """)
+ .replace(/'/g, "'");
+}
+
+/**
+ * Index hierarchy nodes by a stable key path for quick lookup.
+ */
+function codepad_index_hierarchy(node) {
+ scene_node_index = {};
+ if (!node) {
+ return;
+ }
+ (function walk(current, parentKey, index) {
+ if (!current) {
+ return;
+ }
+ if (current._synthetic) {
+ if (current.children) {
+ for (var i = 0; i < current.children.length; i++) {
+ walk(current.children[i], parentKey || "", i);
+ }
+ }
+ return;
+ }
+ var id = (current.props && current.props.id) || current.id || current.name || "node";
+ var key = (parentKey ? parentKey + "/" : "") + id;
+ current._key = key;
+ scene_node_index[key] = current;
+ if (current.children) {
+ for (var i = 0; i < current.children.length; i++) {
+ walk(current.children[i], key, i);
+ }
+ }
+ })(node, "", 0);
+}
+
+/**
+ * Build a DOM subtree for a hierarchy node.
+ */
+function codepad_build_tree_node(node) {
+ var wrapper = document.createElement("div");
+ var hasChildren = node.children && node.children.length;
+ wrapper.className = "tree-node" + (hasChildren ? " is-expanded" : "");
+
+ var item = document.createElement("div");
+ item.className = "tree-item";
+ item.dataset.key = node._key || "";
+
+ var caret = document.createElement("span");
+ caret.className = "tree-caret";
+ if (!hasChildren) {
+ caret.style.visibility = "hidden";
+ }
+ item.appendChild(caret);
+
+ var label = document.createElement("span");
+ label.className = "tree-label";
+ label.textContent = (node.props && node.props.id) || node.id || node.name || "(unnamed)";
+ item.appendChild(label);
+
+ if (node.type) {
+ var meta = document.createElement("span");
+ meta.className = "tree-meta";
+ meta.textContent = node.type;
+ item.appendChild(meta);
+ }
+
+ wrapper.appendChild(item);
+
+ if (hasChildren) {
+ var children = document.createElement("div");
+ children.className = "tree-children";
+ for (var i = 0; i < node.children.length; i++) {
+ children.appendChild(codepad_build_tree_node(node.children[i]));
+ }
+ wrapper.appendChild(children);
+ }
+
+ return wrapper;
+}
+
+/**
+ * Render the hierarchy tree and update selection.
+ */
+function codepad_render_hierarchy(tree) {
+ var container = document.getElementById("hierarchy-tree");
+ if (!container) {
+ return;
+ }
+ container.innerHTML = "";
+ if (!tree) {
+ return;
+ }
+ if (tree._synthetic && tree.children) {
+ for (var i = 0; i < tree.children.length; i++) {
+ container.appendChild(codepad_build_tree_node(tree.children[i]));
+ }
+ } else {
+ container.appendChild(codepad_build_tree_node(tree));
+ }
+ codepad_bind_hierarchy_events();
+ if (scene_selected_path && scene_node_index[scene_selected_path]) {
+ codepad_select_node(scene_selected_path);
+ } else if (tree._synthetic && tree.children && tree.children.length && tree.children[0]._key) {
+ codepad_select_node(tree.children[0]._key);
+ } else if (tree._key) {
+ codepad_select_node(tree._key);
+ }
+}
+
+/**
+ * Bind click handlers for expand/collapse and selection.
+ */
+function codepad_bind_hierarchy_events() {
+ var container = document.getElementById("hierarchy-tree");
+ if (!container || container._codepadBound) {
+ return;
+ }
+ container._codepadBound = true;
+ container.addEventListener("click", function (event) {
+ var caret = event.target.closest(".tree-caret");
+ if (caret) {
+ var nodeElem = caret.closest(".tree-node");
+ if (nodeElem && nodeElem.classList.contains("is-expanded")) {
+ nodeElem.classList.remove("is-expanded");
+ nodeElem.classList.add("is-collapsed");
+ } else if (nodeElem && nodeElem.classList.contains("is-collapsed")) {
+ nodeElem.classList.remove("is-collapsed");
+ nodeElem.classList.add("is-expanded");
+ }
+ event.stopPropagation();
+ return;
+ }
+ var item = event.target.closest(".tree-item");
+ if (!item) {
+ return;
+ }
+ var key = item.dataset.key;
+ if (!key) {
+ return;
+ }
+ codepad_select_node(key);
+ });
+}
+
+/**
+ * Select a node by key and show its properties.
+ */
+function codepad_select_node(key) {
+ var container = document.getElementById("hierarchy-tree");
+ if (!container) {
+ return;
+ }
+ var previous = container.querySelector(".tree-item.is-selected");
+ if (previous) {
+ previous.classList.remove("is-selected");
+ }
+ var next = container.querySelector('.tree-item[data-key="' + key + '"]');
+ if (next) {
+ next.classList.add("is-selected");
+ }
+ scene_selected_path = key;
+ codepad_render_properties(scene_node_index[key]);
+}
+
+/**
+ * Format a property value into a readable string.
+ */
+function codepad_format_prop_value(value) {
+ if (value === null || value === undefined) {
+ return "null";
+ }
+ if (Array.isArray(value)) {
+ return "[" + value.map(codepad_format_prop_value).join(", ") + "]";
+ }
+ if (typeof value === "object") {
+ try {
+ return JSON.stringify(value);
+ } catch (err) {
+ return String(value);
+ }
+ }
+ return String(value);
+}
+
+/**
+ * Render or update the properties list for a selected node.
+ */
+function codepad_render_properties(node) {
+ var container = document.getElementById("properties-list");
+ if (!container) {
+ return;
+ }
+ if (!node) {
+ container.innerHTML = "";
+ container._codepadNodeKey = null;
+ container._codepadKeySig = null;
+ container._codepadValueEls = null;
+ container._codepadRowEls = null;
+ container._codepadHeaderEl = null;
+ return;
+ }
+
+ var props = {};
+ if (node.props) {
+ for (var key in node.props) {
+ if (node.props.hasOwnProperty(key)) {
+ props[key] = node.props[key];
+ }
+ }
+ }
+ if (!props.id) {
+ props.id = (node.props && node.props.id) || node.id || node.name || node._key || "node";
+ }
+
+ var priority = ["id", "name", "path", "url", "position", "rotation", "scale", "size", "pivot", "anchorPoint", "visible", "enabled", "layer"];
+ var keys = Object.keys(props).sort();
+ keys.sort(function (a, b) {
+ var ai = priority.indexOf(a);
+ var bi = priority.indexOf(b);
+ if (ai === -1 && bi === -1) {
+ return a.localeCompare(b);
+ }
+ if (ai === -1) {
+ return 1;
+ }
+ if (bi === -1) {
+ return -1;
+ }
+ return ai - bi;
+ });
+
+ var node_key = node._key || props.id || "node";
+ container._codepadNodeKey = node_key;
+
+ if (!container._codepadHeaderEl) {
+ var header = document.createElement("div");
+ header.className = "properties-header";
+ container.appendChild(header);
+ container._codepadHeaderEl = header;
+ }
+ container._codepadHeaderEl.textContent = props.id || "Node";
+
+ if (!container._codepadRowEls) {
+ container._codepadRowEls = {};
+ }
+ if (!container._codepadValueEls) {
+ container._codepadValueEls = {};
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var row = container._codepadRowEls[key];
+ var valueSpan = container._codepadValueEls[key];
+ if (!row || !valueSpan) {
+ row = document.createElement("div");
+ row.className = "prop-row";
+
+ var keySpan = document.createElement("span");
+ keySpan.className = "prop-key";
+ keySpan.textContent = key;
+
+ valueSpan = document.createElement("span");
+ valueSpan.className = "prop-value";
+
+ row.appendChild(keySpan);
+ row.appendChild(valueSpan);
+ container.appendChild(row);
+
+ container._codepadRowEls[key] = row;
+ container._codepadValueEls[key] = valueSpan;
+ }
+ row.style.display = "grid";
+ valueSpan.textContent = codepad_format_prop_value(props[key]);
+ }
+
+ for (var existing in container._codepadRowEls) {
+ if (container._codepadRowEls.hasOwnProperty(existing)) {
+ if (keys.indexOf(existing) === -1) {
+ container._codepadRowEls[existing].style.display = "none";
+ }
+ }
+ }
+}
diff --git a/codepad/rendering/custom.render_script b/codepad/rendering/custom.render_script
index f69ce7a..6f5e278 100644
--- a/codepad/rendering/custom.render_script
+++ b/codepad/rendering/custom.render_script
@@ -1,57 +1,273 @@
+-- Copyright 2020-2026 The Defold Foundation
+-- Copyright 2014-2020 King
+-- Copyright 2009-2014 Ragnar Svensson, Christian Murray
+-- Licensed under the Defold License version 1.0 (the "License"); you may not use
+-- this file except in compliance with the License.
+--
+-- You may obtain a copy of the License, together with FAQs at
+-- https://www.defold.com/license
+--
+-- Unless required by applicable law or agreed to in writing, software distributed
+-- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+-- CONDITIONS OF ANY KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations under the License.
+
+--
+-- message constants
+--
+local MSG_CLEAR_COLOR = hash("clear_color")
+local MSG_WINDOW_RESIZED = hash("window_resized")
+local MSG_SET_VIEW_PROJ = hash("set_view_projection")
+local MSG_SET_CAMERA_PROJ = hash("use_camera_projection")
+local MSG_USE_STRETCH_PROJ = hash("use_stretch_projection")
+local MSG_USE_FIXED_PROJ = hash("use_fixed_projection")
+local MSG_USE_FIXED_FIT_PROJ = hash("use_fixed_fit_projection")
+
+local DEFAULT_NEAR = -1
+local DEFAULT_FAR = 1
+local DEFAULT_ZOOM = 1
+
+--
+-- projection that centers content with maintained aspect ratio and optional zoom
+--
+local function get_fixed_projection(camera, state)
+ camera.zoom = camera.zoom or DEFAULT_ZOOM
+ local projected_width = state.window_width / camera.zoom
+ local projected_height = state.window_height / camera.zoom
+ local left = -(projected_width - state.width) / 2
+ local bottom = -(projected_height - state.height) / 2
+ local right = left + projected_width
+ local top = bottom + projected_height
+ return vmath.matrix4_orthographic(left, right, bottom, top, camera.near, camera.far)
+end
+--
+-- projection that centers and fits content with maintained aspect ratio
+--
+local function get_fixed_fit_projection(camera, state)
+ camera.zoom = math.min(state.window_width / state.width, state.window_height / state.height)
+ return get_fixed_projection(camera, state)
+end
+--
+-- projection that stretches content
+--
+local function get_stretch_projection(camera, state)
+ return vmath.matrix4_orthographic(0, state.width, 0, state.height, camera.near, camera.far)
+end
+--
+-- projection for gui
+--
+local function get_gui_projection(camera, state)
+ return vmath.matrix4_orthographic(0, state.window_width, 0, state.window_height, camera.near, camera.far)
+end
+
+local function update_clear_color(state, color)
+ if color then
+ state.clear_buffers[graphics.BUFFER_TYPE_COLOR0_BIT] = color
+ end
+end
+
+local function update_camera(camera, state)
+ if camera.projection_fn then
+ camera.proj = camera.projection_fn(camera, state)
+ camera.options.frustum = camera.proj * camera.view
+ end
+end
+
+local function update_state(state)
+ state.window_width = render.get_window_width()
+ state.window_height = render.get_window_height()
+ state.valid = state.window_width > 0 and state.window_height > 0
+ if not state.valid then
+ return false
+ end
+ -- Make sure state updated only once when resize window
+ if state.window_width == state.prev_window_width and state.window_height == state.prev_window_height then
+ return true
+ end
+ state.prev_window_width = state.window_width
+ state.prev_window_height = state.window_height
+ state.width = render.get_width()
+ state.height = render.get_height()
+ for _, camera in pairs(state.cameras) do
+ update_camera(camera, state)
+ end
+ return true
+end
+
+local function init_camera(camera, projection_fn, near, far, zoom)
+ camera.view = vmath.matrix4()
+ camera.near = near == nil and DEFAULT_NEAR or near
+ camera.far = far == nil and DEFAULT_FAR or far
+ camera.zoom = zoom == nil and DEFAULT_ZOOM or zoom
+ camera.projection_fn = projection_fn
+end
+
+local function create_predicates(...)
+ local arg = {...}
+ local predicates = {}
+ for _, predicate_name in pairs(arg) do
+ predicates[predicate_name] = render.predicate({predicate_name})
+ end
+ return predicates
+end
+
+local function create_camera(state, name, is_main_camera)
+ local camera = {}
+ camera.options = {}
+ state.cameras[name] = camera
+ if is_main_camera then
+ state.main_camera = camera
+ end
+ return camera
+end
+
+local function create_state()
+ local state = {}
+ local color = vmath.vector4(0, 0, 0, 0)
+ color.x = sys.get_config_number("render.clear_color_red", 0)
+ color.y = sys.get_config_number("render.clear_color_green", 0)
+ color.z = sys.get_config_number("render.clear_color_blue", 0)
+ color.w = sys.get_config_number("render.clear_color_alpha", 0)
+ state.clear_buffers = {
+ [graphics.BUFFER_TYPE_COLOR0_BIT] = color,
+ [graphics.BUFFER_TYPE_DEPTH_BIT] = 1,
+ [graphics.BUFFER_TYPE_STENCIL_BIT] = 0
+ }
+ state.cameras = {}
+ return state
+end
+
+local function set_camera_world(state)
+ local camera_components = camera.get_cameras()
+
+ -- This will set the last enabled camera from the stack of camera components
+ if #camera_components > 0 then
+ for i = #camera_components, 1, -1 do
+ if camera.get_enabled(camera_components[i]) then
+ local camera_component = state.cameras.camera_component
+ camera_component.camera = camera_components[i]
+ render.set_camera(camera_component.camera, { use_frustum = true })
+ -- The frustum will be overridden by the render.set_camera call,
+ -- so we don't need to return anything here other than an empty table.
+ return camera_component.options
+ end
+ end
+ end
+
+ -- If no active camera was found, we use the default main "camera world" camera
+ local camera_world = state.cameras.camera_world
+ render.set_view(camera_world.view)
+ render.set_projection(camera_world.proj)
+ return camera_world.options
+end
+
+local function reset_camera_world(state)
+ -- unbind the camera if a camera component is used
+ if state.cameras.camera_component.camera then
+ state.cameras.camera_component.camera = nil
+ render.set_camera()
+ end
+end
+
function init(self)
- self.tile_pred = render.predicate({"tile"})
- self.gui_pred = render.predicate({"gui"})
- self.text_pred = render.predicate({"text"})
- self.particle_pred = render.predicate({"particle"})
+ self.predicates = create_predicates("tile", "gui", "particle", "model", "debug_text")
- self.clear_color = vmath.vector4(44/255, 46/255, 51/255, 1)
+ -- default is stretch projection. copy from builtins and change for different projection
+ -- or send a message to the render script to change projection:
+ -- msg.post("@render:", "use_stretch_projection", { near = -1, far = 1 })
+ -- msg.post("@render:", "use_fixed_projection", { near = -1, far = 1, zoom = 2 })
+ -- msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
- self.view = vmath.matrix4()
+ local state = create_state()
+ self.state = state
+
+ local camera_world = create_camera(state, "camera_world", true)
+ init_camera(camera_world, get_stretch_projection)
+ local camera_gui = create_camera(state, "camera_gui")
+ init_camera(camera_gui, get_gui_projection)
+ -- Create a special camera that wraps camera components (if they exist)
+ -- It will take precedence over any other camera, and not change from messages
+ local camera_component = create_camera(state, "camera_component")
+ update_state(state)
end
function update(self)
+ local state = self.state
+ if not state.valid then
+ if not update_state(state) then
+ return
+ end
+ end
+
+ local predicates = self.predicates
+ -- clear screen buffers
+ --
+ -- turn on depth_mask before `render.clear()` to clear it as well
render.set_depth_mask(true)
render.set_stencil_mask(0xff)
- render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})
-
- render.set_viewport(0, 0, render.get_window_width(), render.get_window_height())
-
- -- draw game objects
- local hw = render.get_window_width() / 2
- local hh = render.get_window_height() / 2
- local projection = vmath.matrix4_orthographic(-hw, hw, -hh, hh, -1, 1)
- local frustum = projection * self.view
- render.set_view(self.view)
- render.set_projection(projection)
-
+ render.clear(state.clear_buffers)
+
+ -- setup camera view and projection
+ --
+ local draw_options_world = set_camera_world(state)
+ render.set_viewport(0, 0, state.window_width, state.window_height)
+
+ -- set states used for all the world predicates
+ render.set_blend_func(graphics.BLEND_FACTOR_SRC_ALPHA, graphics.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA)
+ render.enable_state(graphics.STATE_DEPTH_TEST)
+
+ -- render `model` predicate for default 3D material
+ --
+ render.enable_state(graphics.STATE_CULL_FACE)
+ render.draw(predicates.model, draw_options_world)
render.set_depth_mask(false)
- render.disable_state(render.STATE_DEPTH_TEST)
- render.disable_state(render.STATE_STENCIL_TEST)
- render.disable_state(render.STATE_CULL_FACE)
- render.enable_state(render.STATE_BLEND)
- render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
+ render.disable_state(graphics.STATE_CULL_FACE)
+ -- render the other components: sprites, tilemaps, particles etc
+ --
+ render.enable_state(graphics.STATE_BLEND)
render.draw_debug3d()
- render.draw(self.tile_pred, { frustum = frustum })
- render.draw(self.particle_pred, { frustum = frustum })
+ render.draw(predicates.tile, draw_options_world)
+ render.draw(predicates.particle, draw_options_world)
+ render.disable_state(graphics.STATE_DEPTH_TEST)
- -- draw gui
- local view_gui = vmath.matrix4()
- local proj_gui = vmath.matrix4_orthographic(0, render.get_window_width(), 0, render.get_window_height(), -1, 1)
- local frustum_gui = proj_gui * view_gui
- render.set_view(view_gui)
- render.set_projection(proj_gui)
+ reset_camera_world(state)
- render.enable_state(render.STATE_STENCIL_TEST)
- render.draw(self.gui_pred, {frustum = frustum_gui})
- render.draw(self.text_pred, {frustum = frustum_gui})
- render.disable_state(render.STATE_STENCIL_TEST)
+ -- render GUI
+ --
+ local camera_gui = state.cameras.camera_gui
+ render.set_view(camera_gui.view)
+ render.set_projection(camera_gui.proj)
+
+ render.enable_state(graphics.STATE_STENCIL_TEST)
+ render.draw(predicates.gui, camera_gui.options)
+ render.draw(predicates.debug_text, camera_gui.options)
+ render.disable_state(graphics.STATE_STENCIL_TEST)
+ render.disable_state(graphics.STATE_BLEND)
end
function on_message(self, message_id, message)
- if message_id == hash("clear_color") then
- self.clear_color = message.color
- elseif message_id == hash("set_view_projection") then
- self.view = message.view
+ local state = self.state
+ local camera = state.main_camera
+
+ if message_id == MSG_CLEAR_COLOR then
+ update_clear_color(state, message.color)
+ elseif message_id == MSG_WINDOW_RESIZED then
+ update_state(state)
+ elseif message_id == MSG_SET_VIEW_PROJ then
+ camera.view = message.view
+ self.camera_projection = message.projection or vmath.matrix4()
+ update_camera(camera, state)
+ elseif message_id == MSG_SET_CAMERA_PROJ then
+ camera.projection_fn = function() return self.camera_projection end
+ elseif message_id == MSG_USE_STRETCH_PROJ then
+ init_camera(camera, get_stretch_projection, message.near, message.far)
+ update_camera(camera, state)
+ elseif message_id == MSG_USE_FIXED_PROJ then
+ init_camera(camera, get_fixed_projection, message.near, message.far, message.zoom)
+ update_camera(camera, state)
+ elseif message_id == MSG_USE_FIXED_FIT_PROJ then
+ init_camera(camera, get_fixed_fit_projection, message.near, message.far)
+ update_camera(camera, state)
end
end
diff --git a/codepad/template.html b/codepad/template.html
index 92fe3f7..ae0c063 100644
--- a/codepad/template.html
+++ b/codepad/template.html
@@ -32,12 +32,12 @@
-
-
-
-
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
@@ -69,8 +84,22 @@
},
engine_arguments: [{{#DEFOLD_ENGINE_ARGUMENTS}}"{{.}}",{{/DEFOLD_ENGINE_ARGUMENTS}}],
custom_heap_size: {{DEFOLD_HEAP_SIZE}},
+ full_screen_container: "#app-container",
+ resize_window_callback: function() {
+ if (typeof fix_canvas_size === "function") {
+ fix_canvas_size();
+ }
+ },
disable_context_menu: true
}
+ if (typeof CUSTOM_PARAMETERS !== "undefined") {
+ CUSTOM_PARAMETERS.full_screen_container = "#app-container";
+ CUSTOM_PARAMETERS.resize_window_callback = function() {
+ if (typeof fix_canvas_size === "function") {
+ fix_canvas_size();
+ }
+ };
+ }
Module['onRuntimeInitialized'] = function() {
Module.runApp("canvas", extra_params);
@@ -88,6 +117,7 @@
+
diff --git a/game.project b/game.project
index 09c2690..b5a0bc4 100644
--- a/game.project
+++ b/game.project
@@ -1,9 +1,9 @@
[project]
title = DefoldCodePad
-version = 0.1
+version = 0.2
bundle_resources = /codepad/bundle_resources/
bundle_exclude_resources =
-custom_resources = main/scripts
+custom_resources = /main/scripts
[bootstrap]
main_collection = /main/main.collectionc
@@ -28,4 +28,10 @@ shared_state = 1
[html5]
htmlfile = /codepad/template.html
+scale_mode = stretch
+
+[render]
+clear_color_green = 0.18
+clear_color_red = 0.17
+clear_color_blue = 0.2
diff --git a/main/codepads/gui_nodes/gui_nodes.lua b/main/codepads/gui_nodes/gui_nodes.lua
index d82cc4c..5590094 100644
--- a/main/codepads/gui_nodes/gui_nodes.lua
+++ b/main/codepads/gui_nodes/gui_nodes.lua
@@ -51,7 +51,7 @@ end
return {
name = "Gui Nodes",
url = "#cp_gui_nodes",
- grid = false,
+ grid = true,
scripts = {
{
url = "cp_gui_nodes:/go#gui",
diff --git a/main/main.collection b/main/main.collection
index 1fd357e..72c8caa 100644
--- a/main/main.collection
+++ b/main/main.collection
@@ -36,5 +36,16 @@ embedded_instances {
" data: \"collection: \\\"/main/codepads/label/label.collection\\\"\\n"
"\"\n"
"}\n"
+ "embedded_components {\n"
+ " id: \"camera\"\n"
+ " type: \"camera\"\n"
+ " data: \"aspect_ratio: 1.0\\n"
+ "fov: 0.7854\\n"
+ "near_z: -1000.0\\n"
+ "far_z: 1000.0\\n"
+ "orthographic_projection: 1\\n"
+ "orthographic_mode: ORTHO_MODE_AUTO_FIT\\n"
+ "\"\n"
+ "}\n"
""
}
diff --git a/main/scripts/factory/factory.script b/main/scripts/factory/factory.script
index ebc2845..2b6ae3a 100644
--- a/main/scripts/factory/factory.script
+++ b/main/scripts/factory/factory.script
@@ -16,7 +16,8 @@ end
function on_input(self, action_id, action)
if action_id == hash("mouse_button_left") and action.released then
- local id = factory.create("#factory", vmath.vector3(action.x, action.y, 0))
+ local world_pos = camera.screen_xy_to_world(action.screen_x, action.screen_y)
+ local id = factory.create("#factory", world_pos)
print(id)
end
end
diff --git a/scene_dump/ext.manifest b/scene_dump/ext.manifest
new file mode 100644
index 0000000..218a528
--- /dev/null
+++ b/scene_dump/ext.manifest
@@ -0,0 +1 @@
+name: "codepad_scene_dump"
diff --git a/scene_dump/src/scene_dump.cpp b/scene_dump/src/scene_dump.cpp
new file mode 100644
index 0000000..50504c3
--- /dev/null
+++ b/scene_dump/src/scene_dump.cpp
@@ -0,0 +1,450 @@
+#define EXTENSION_NAME codepad_scene_dump
+#define LIB_NAME "codepad_scene_dump"
+#define MODULE_NAME "codepad_scene_dump"
+#ifndef DLIB_LOG_DOMAIN
+#define DLIB_LOG_DOMAIN LIB_NAME
+#endif
+#include
+
+#include
+#include
+
+#if defined(DM_PLATFORM_HTML5)
+#include
+#endif
+
+#include
+#include
+#include
+
+namespace
+{
+ struct SceneDumpContext
+ {
+ dmGameObject::HRegister m_Register;
+ bool m_Initialized;
+ };
+
+ SceneDumpContext g_Context = { 0, false };
+ std::string g_Buffer;
+ std::string g_FilterId;
+
+ static void AppendJsonString(std::string& out, const char* value)
+ {
+ out.push_back('"');
+ if (value)
+ {
+ const unsigned char* p = (const unsigned char*)value;
+ while (*p)
+ {
+ unsigned char c = *p++;
+ switch (c)
+ {
+ case '"': out.append("\\\""); break;
+ case '\\': out.append("\\\\"); break;
+ case '\b': out.append("\\b"); break;
+ case '\f': out.append("\\f"); break;
+ case '\n': out.append("\\n"); break;
+ case '\r': out.append("\\r"); break;
+ case '\t': out.append("\\t"); break;
+ default:
+ if (c < 0x20)
+ {
+ char buf[7];
+ snprintf(buf, sizeof(buf), "\\u%04x", (unsigned int)c);
+ out.append(buf);
+ }
+ else
+ {
+ out.push_back((char)c);
+ }
+ break;
+ }
+ }
+ }
+ out.push_back('"');
+ }
+
+ static void AppendJsonNumber(std::string& out, double value)
+ {
+ char buffer[64];
+ snprintf(buffer, sizeof(buffer), "%.6g", value);
+ out.append(buffer);
+ }
+
+ static void AppendJsonBool(std::string& out, bool value)
+ {
+ out.append(value ? "true" : "false");
+ }
+
+ static void AppendField(std::string& out, const char* key, const char* value, bool* first)
+ {
+ if (!*first)
+ {
+ out.push_back(',');
+ }
+ *first = false;
+ AppendJsonString(out, key);
+ out.push_back(':');
+ if (value)
+ {
+ AppendJsonString(out, value);
+ }
+ else
+ {
+ out.append("null");
+ }
+ }
+
+ static void AppendJsonVector(std::string& out, const float* value, int count)
+ {
+ out.push_back('[');
+ for (int i = 0; i < count; ++i)
+ {
+ if (i > 0)
+ {
+ out.push_back(',');
+ }
+ AppendJsonNumber(out, value[i]);
+ }
+ out.push_back(']');
+ }
+
+ static void AppendPropertyValue(std::string& out, dmGameObject::SceneNodeProperty* property)
+ {
+ switch (property->m_Type)
+ {
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_HASH:
+ {
+ const char* value = dmHashReverseSafe64(property->m_Value.m_Hash);
+ if (value)
+ {
+ AppendJsonString(out, value);
+ }
+ else
+ {
+ out.append("null");
+ }
+ break;
+ }
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_NUMBER:
+ AppendJsonNumber(out, property->m_Value.m_Number);
+ break;
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_BOOLEAN:
+ AppendJsonBool(out, property->m_Value.m_Bool);
+ break;
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_URL:
+ AppendJsonString(out, property->m_Value.m_URL);
+ break;
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_TEXT:
+ AppendJsonString(out, property->m_Value.m_Text);
+ break;
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_VECTOR3:
+ AppendJsonVector(out, property->m_Value.m_V4, 3);
+ break;
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_VECTOR4:
+ case dmGameObject::SCENE_NODE_PROPERTY_TYPE_QUAT:
+ AppendJsonVector(out, property->m_Value.m_V4, 4);
+ break;
+ default:
+ out.append("null");
+ break;
+ }
+ }
+
+ static void AppendProperty(std::string& out, dmGameObject::SceneNodeProperty* property, bool* first)
+ {
+ const char* key = dmHashReverseSafe64(property->m_NameHash);
+ if (!key || key[0] == '\0')
+ {
+ return;
+ }
+ if (strcmp(key, "id") == 0 || strcmp(key, "type") == 0 || strcmp(key, "resource") == 0 || strcmp(key, "script_id") == 0)
+ {
+ return;
+ }
+ if (!*first)
+ {
+ out.push_back(',');
+ }
+ *first = false;
+ AppendJsonString(out, key);
+ out.push_back(':');
+ AppendPropertyValue(out, property);
+ }
+
+ static void GetNodeInfo(dmGameObject::SceneNode* node, dmhash_t& name, dmhash_t& type)
+ {
+ static dmhash_t hash_id = dmHashString64("id");
+ static dmhash_t hash_type = dmHashString64("type");
+
+ dmGameObject::SceneNodePropertyIterator pit = TraverseIterateProperties(node);
+ while (dmGameObject::TraverseIteratePropertiesNext(&pit))
+ {
+ if (pit.m_Property.m_NameHash == hash_id)
+ {
+ name = pit.m_Property.m_Value.m_Hash;
+ }
+ else if (pit.m_Property.m_NameHash == hash_type)
+ {
+ type = pit.m_Property.m_Value.m_Hash;
+ }
+ }
+ }
+
+ static const char* NormalizeType(const char* type_str, std::string& out)
+ {
+ if (!type_str)
+ {
+ return 0;
+ }
+ size_t len = strlen(type_str);
+ if (len > 0 && type_str[len - 1] == 'c')
+ {
+ out.assign(type_str, len - 1);
+ return out.c_str();
+ }
+ return type_str;
+ }
+
+ static bool IsCollectionProxyType(const char* type_str)
+ {
+ if (!type_str)
+ {
+ return false;
+ }
+ return strncmp(type_str, "collectionproxy", 15) == 0;
+ }
+
+ static bool MatchesFilter(const char* name_str, const char* filter)
+ {
+ if (!filter || filter[0] == '\0')
+ {
+ return true;
+ }
+ if (!name_str || name_str[0] == '\0')
+ {
+ return false;
+ }
+ if (strcmp(name_str, filter) == 0)
+ {
+ return true;
+ }
+ if (filter[0] == '#' && strcmp(name_str, filter + 1) == 0)
+ {
+ return true;
+ }
+ if (name_str[0] == '#' && strcmp(name_str + 1, filter) == 0)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ static bool FindCollectionProxyById(dmGameObject::SceneNode* node, const char* filter, dmGameObject::SceneNode* out)
+ {
+ dmhash_t name_hash = 0;
+ dmhash_t type_hash = 0;
+ GetNodeInfo(node, name_hash, type_hash);
+
+ const char* name_str = name_hash ? dmHashReverseSafe64(name_hash) : 0;
+ const char* type_str = type_hash ? dmHashReverseSafe64(type_hash) : 0;
+
+ if (IsCollectionProxyType(type_str) && MatchesFilter(name_str, filter))
+ {
+ *out = *node;
+ return true;
+ }
+
+ dmGameObject::SceneNodeIterator it = dmGameObject::TraverseIterateChildren(node);
+ while (dmGameObject::TraverseIterateNext(&it))
+ {
+ dmGameObject::SceneNode child = it.m_Node;
+ if (FindCollectionProxyById(&child, filter, out))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static bool FindFirstCollectionProxy(dmGameObject::SceneNode* node, dmGameObject::SceneNode* out)
+ {
+ dmhash_t name_hash = 0;
+ dmhash_t type_hash = 0;
+ GetNodeInfo(node, name_hash, type_hash);
+
+ const char* type_str = type_hash ? dmHashReverseSafe64(type_hash) : 0;
+ if (IsCollectionProxyType(type_str))
+ {
+ *out = *node;
+ return true;
+ }
+
+ dmGameObject::SceneNodeIterator it = dmGameObject::TraverseIterateChildren(node);
+ while (dmGameObject::TraverseIterateNext(&it))
+ {
+ dmGameObject::SceneNode child = it.m_Node;
+ if (FindFirstCollectionProxy(&child, out))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void DumpNode(std::string& out, dmGameObject::SceneNode* node)
+ {
+ dmhash_t name_hash = 0;
+ dmhash_t type_hash = 0;
+ GetNodeInfo(node, name_hash, type_hash);
+
+ const char* name_str = name_hash ? dmHashReverseSafe64(name_hash) : 0;
+ if (!name_str || name_str[0] == '\0')
+ {
+ name_str = "node";
+ }
+
+ const char* raw_type_str = type_hash ? dmHashReverseSafe64(type_hash) : 0;
+ std::string type_clean;
+ const char* type_str = NormalizeType(raw_type_str, type_clean);
+
+ out.push_back('{');
+ bool first = true;
+ AppendField(out, "type", type_str, &first);
+
+ out.append(",\"props\":{");
+ bool props_first = true;
+ AppendField(out, "id", name_str, &props_first);
+ if (type_str)
+ {
+ AppendField(out, "type", type_str, &props_first);
+ }
+ dmGameObject::SceneNodePropertyIterator pit = TraverseIterateProperties(node);
+ while (dmGameObject::TraverseIteratePropertiesNext(&pit))
+ {
+ AppendProperty(out, &pit.m_Property, &props_first);
+ }
+ out.push_back('}');
+
+ out.append(",\"children\":[");
+ dmGameObject::SceneNodeIterator it = dmGameObject::TraverseIterateChildren(node);
+ bool first_child = true;
+ while (dmGameObject::TraverseIterateNext(&it))
+ {
+ if (!first_child)
+ {
+ out.push_back(',');
+ }
+ first_child = false;
+ DumpNode(out, &it.m_Node);
+ }
+ out.push_back(']');
+ out.push_back('}');
+ }
+
+ static const char* BuildSceneJson()
+ {
+ g_Buffer.clear();
+ if (!g_Context.m_Initialized)
+ {
+ g_Buffer.assign("null");
+ return g_Buffer.c_str();
+ }
+
+ dmGameObject::SceneNode root;
+ if (!dmGameObject::TraverseGetRoot(g_Context.m_Register, &root))
+ {
+ g_Buffer.assign("null");
+ return g_Buffer.c_str();
+ }
+
+ g_Buffer.reserve(4096);
+ dmGameObject::SceneNode target = root;
+ bool found_target = false;
+ if (!g_FilterId.empty())
+ {
+ found_target = FindCollectionProxyById(&root, g_FilterId.c_str(), &target);
+ if (!found_target)
+ {
+ found_target = FindFirstCollectionProxy(&root, &target);
+ }
+ }
+ if (found_target)
+ {
+ dmhash_t type_hash = 0;
+ dmhash_t name_hash = 0;
+ GetNodeInfo(&target, name_hash, type_hash);
+ const char* type_str = type_hash ? dmHashReverseSafe64(type_hash) : 0;
+ if (IsCollectionProxyType(type_str))
+ {
+ g_Buffer.push_back('[');
+ dmGameObject::SceneNodeIterator it = dmGameObject::TraverseIterateChildren(&target);
+ bool first_child = true;
+ while (dmGameObject::TraverseIterateNext(&it))
+ {
+ if (!first_child)
+ {
+ g_Buffer.push_back(',');
+ }
+ first_child = false;
+ DumpNode(g_Buffer, &it.m_Node);
+ }
+ g_Buffer.push_back(']');
+ }
+ else
+ {
+ DumpNode(g_Buffer, &target);
+ }
+ }
+ else
+ {
+ DumpNode(g_Buffer, &root);
+ }
+ return g_Buffer.c_str();
+ }
+}
+
+#if defined(DM_PLATFORM_HTML5)
+extern "C" EMSCRIPTEN_KEEPALIVE const char* CodepadSceneDump_DumpJson()
+{
+ return BuildSceneJson();
+}
+extern "C" EMSCRIPTEN_KEEPALIVE void CodepadSceneDump_SetFilter(const char* filter)
+{
+ g_FilterId = filter ? filter : "";
+}
+#else
+extern "C" const char* CodepadSceneDump_DumpJson()
+{
+ return 0;
+}
+extern "C" void CodepadSceneDump_SetFilter(const char* filter)
+{
+ (void)filter;
+}
+#endif
+
+static dmExtension::Result AppInitializeSceneDump(dmExtension::AppParams* params)
+{
+ g_Context.m_Register = dmEngine::GetGameObjectRegister(params);
+ g_Context.m_Initialized = true;
+ return dmExtension::RESULT_OK;
+}
+
+static dmExtension::Result InitializeSceneDump(dmExtension::Params* params)
+{
+ return dmExtension::RESULT_OK;
+}
+
+static dmExtension::Result AppFinalizeSceneDump(dmExtension::AppParams* params)
+{
+ return dmExtension::RESULT_OK;
+}
+
+static dmExtension::Result FinalizeSceneDump(dmExtension::Params* params)
+{
+ return dmExtension::RESULT_OK;
+}
+
+DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, AppInitializeSceneDump, AppFinalizeSceneDump, InitializeSceneDump, 0, 0, FinalizeSceneDump)