summaryrefslogtreecommitdiff
path: root/tests/servers
diff options
context:
space:
mode:
authorAaron Franke <arnfranke@yahoo.com>2021-11-06 16:05:33 -0500
committerAaron Franke <arnfranke@yahoo.com>2021-11-07 00:43:31 -0600
commit99a282f6319d0c7642f589163b955a636610719a (patch)
tree7b0ebfc160bb684c12760f52c4fa855bdc997a1e /tests/servers
parent9f46ce86523e01435ad34de467de586485448278 (diff)
Move and organize tests into subfolders
Diffstat (limited to 'tests/servers')
-rw-r--r--tests/servers/test_physics_2d.cpp398
-rw-r--r--tests/servers/test_physics_2d.h41
-rw-r--r--tests/servers/test_physics_3d.cpp410
-rw-r--r--tests/servers/test_physics_3d.h41
-rw-r--r--tests/servers/test_render.cpp232
-rw-r--r--tests/servers/test_render.h41
-rw-r--r--tests/servers/test_shader_lang.cpp360
-rw-r--r--tests/servers/test_shader_lang.h41
-rw-r--r--tests/servers/test_text_server.h296
9 files changed, 1860 insertions, 0 deletions
diff --git a/tests/servers/test_physics_2d.cpp b/tests/servers/test_physics_2d.cpp
new file mode 100644
index 0000000000..b6360cde8a
--- /dev/null
+++ b/tests/servers/test_physics_2d.cpp
@@ -0,0 +1,398 @@
+/*************************************************************************/
+/* test_physics_2d.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_physics_2d.h"
+
+#include "core/os/main_loop.h"
+#include "servers/physics_server_2d.h"
+#include "servers/rendering_server.h"
+
+static const unsigned char convex_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x6, 0x0, 0x0, 0x0, 0xaa, 0x69, 0x71, 0xde, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x6, 0x62, 0x4b, 0x47, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf9, 0x43, 0xbb, 0x7f, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xdb, 0x6, 0xa, 0x3, 0x13, 0x31, 0x66, 0xa7, 0xac, 0x79, 0x0, 0x0, 0x4, 0xef, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x9b, 0xdd, 0x4e, 0x2a, 0x57, 0x14, 0xc7, 0xf7, 0x1e, 0xc0, 0x19, 0x38, 0x32, 0x80, 0xa, 0x6a, 0xda, 0x18, 0xa3, 0xc6, 0x47, 0x50, 0x7b, 0xa1, 0xd9, 0x36, 0x27, 0x7e, 0x44, 0xed, 0x45, 0x4d, 0x93, 0x3e, 0x40, 0x1f, 0x64, 0x90, 0xf4, 0x1, 0xbc, 0xf0, 0xc2, 0x9c, 0x57, 0x30, 0x4d, 0xbc, 0xa8, 0x6d, 0xc, 0x69, 0x26, 0xb5, 0x68, 0x8b, 0x35, 0x7e, 0x20, 0xb4, 0xf5, 0x14, 0xbf, 0x51, 0x3c, 0x52, 0xe, 0xc, 0xe, 0xc8, 0xf0, 0xb1, 0x7a, 0x51, 0x3d, 0xb1, 0x9e, 0x19, 0x1c, 0x54, 0x70, 0x1c, 0xdc, 0x9, 0x17, 0x64, 0x8, 0xc9, 0xff, 0xb7, 0xd6, 0x7f, 0xcd, 0x3f, 0x2b, 0xd9, 0x8, 0xbd, 0x9c, 0xda, 0x3e, 0xf8, 0x31, 0xff, 0xc, 0x0, 0x8, 0x42, 0x88, 0x9c, 0x9f, 0x9f, 0xbf, 0xa, 0x87, 0xc3, 0xad, 0x7d, 0x7d, 0x7d, 0x7f, 0x23, 0x84, 0x78, 0x8c, 0x31, 0xaf, 0x55, 0x0, 0xc6, 0xc7, 0x14, 0x1e, 0x8f, 0xc7, 0xbf, 0x38, 0x3c, 0x3c, 0x6c, 0x9b, 0x9f, 0x9f, 0x6f, 0xb8, 0x82, 0x9b, 0xee, 0xe8, 0xe8, 0xf8, 0x12, 0x0, 0xbe, 0xd3, 0x2a, 0x8, 0xfc, 0x50, 0xd1, 0xf9, 0x7c, 0x9e, 0x8a, 0x46, 0xa3, 0x5f, 0x9d, 0x9e, 0x9e, 0x7e, 0xb2, 0xb0, 0xb0, 0x60, 0xe5, 0x79, 0x1e, 0xf1, 0xfc, 0x7f, 0x3a, 0x9, 0x21, 0x88, 0x10, 0x82, 0x26, 0x26, 0x26, 0xde, 0x77, 0x75, 0x75, 0x85, 0x59, 0x96, 0xfd, 0x5e, 0x6b, 0x20, 0xf0, 0x7d, 0x85, 0x4b, 0x92, 0xf4, 0xfa, 0xe0, 0xe0, 0xe0, 0xd3, 0xb9, 0xb9, 0xb9, 0x46, 0x49, 0x92, 0xea, 0x6f, 0xa, 0xbf, 0x7d, 0x8, 0x21, 0x68, 0x70, 0x70, 0xb0, 0x38, 0x39, 0x39, 0x79, 0xd6, 0xd9, 0xd9, 0xb9, 0xcf, 0x30, 0xcc, 0xa2, 0xd6, 0xad, 0x21, 0x2b, 0x1c, 0x0, 0x38, 0x41, 0x10, 0xfc, 0xdb, 0xdb, 0xdb, 0x27, 0x1e, 0x8f, 0x27, 0x4b, 0x8, 0x1, 0x84, 0x90, 0xea, 0xf, 0x21, 0x4, 0x3c, 0x1e, 0x4f, 0x76, 0x67, 0x67, 0x67, 0x3f, 0x9f, 0xcf, 0xff, 0x7c, 0x5, 0xf3, 0xd9, 0x0, 0xe0, 0x2, 0x81, 0xc0, 0xa9, 0xdb, 0xed, 0x2e, 0x94, 0x2b, 0x5c, 0xe, 0xc4, 0xca, 0xca, 0x8a, 0x18, 0x8d, 0x46, 0x3, 0x0, 0xc0, 0x69, 0x1e, 0x4, 0x0, 0x90, 0x48, 0x24, 0x12, 0xe4, 0x38, 0xee, 0x41, 0xc2, 0x6f, 0x43, 0xe0, 0x38, 0xe, 0xfc, 0x7e, 0xbf, 0x10, 0x8b, 0xc5, 0xd6, 0x35, 0xd, 0x22, 0x9b, 0xcd, 0x7a, 0x96, 0x97, 0x97, 0x33, 0xf, 0xad, 0x7c, 0x29, 0x10, 0x9b, 0x9b, 0x9b, 0xef, 0x2e, 0x2e, 0x2e, 0x7e, 0xd5, 0x1c, 0x8, 0x0, 0x20, 0xe1, 0x70, 0x38, 0xfc, 0x98, 0xd5, 0x57, 0x2, 0xe1, 0x76, 0xbb, 0xf3, 0xa1, 0x50, 0xe8, 0x38, 0x9b, 0xcd, 0xfe, 0xa2, 0x9, 0x8, 0x0, 0x40, 0x2e, 0x2f, 0x2f, 0x7d, 0x4b, 0x4b, 0x4b, 0xb9, 0x4a, 0x54, 0x5f, 0x9, 0xc4, 0xd2, 0xd2, 0x92, 0xb4, 0xb7, 0xb7, 0xf7, 0x36, 0x97, 0xcb, 0x4d, 0x3d, 0x29, 0x8, 0x0, 0xe0, 0x42, 0xa1, 0xd0, 0x71, 0xb5, 0xc4, 0xdf, 0xb6, 0xc5, 0x93, 0xe, 0x4a, 0x0, 0x20, 0xa9, 0x54, 0xea, 0x37, 0xb7, 0xdb, 0x5d, 0xa8, 0xa6, 0x78, 0x39, 0x10, 0x6b, 0x6b, 0x6b, 0xf1, 0x64, 0x32, 0xb9, 0x5a, 0x55, 0x10, 0x0, 0xc0, 0x6d, 0x6c, 0x6c, 0x9c, 0x57, 0xbb, 0xfa, 0x25, 0x40, 0x14, 0x3, 0x81, 0x40, 0x34, 0x93, 0xc9, 0x2c, 0x57, 0x1c, 0x4, 0x0, 0x90, 0x58, 0x2c, 0xb6, 0x5e, 0xe9, 0xc1, 0x77, 0x1f, 0x10, 0x53, 0x53, 0x53, 0x52, 0xc5, 0x83, 0x14, 0x0, 0x70, 0x7e, 0xbf, 0x5f, 0xd0, 0x42, 0xf5, 0x95, 0x40, 0xf8, 0x7c, 0xbe, 0xcb, 0xa3, 0xa3, 0xa3, 0x3f, 0x1e, 0xbd, 0x1b, 0x0, 0x80, 0x1c, 0x1f, 0x1f, 0x87, 0xb4, 0x56, 0xfd, 0xaa, 0x5, 0x29, 0x51, 0x14, 0xbf, 0xf5, 0xf9, 0x7c, 0x97, 0x5a, 0xad, 0xbe, 0x12, 0x88, 0xf5, 0xf5, 0xf5, 0xd8, 0x83, 0x83, 0x54, 0xb5, 0x42, 0x8f, 0x66, 0x83, 0x94, 0xd6, 0xbd, 0x5f, 0xce, 0x7c, 0x38, 0x3c, 0x3c, 0xfc, 0xb3, 0x50, 0x28, 0xb8, 0xcb, 0x2, 0x1, 0x0, 0xdc, 0xf4, 0xf4, 0xf4, 0xfe, 0x73, 0x15, 0x2f, 0x17, 0xa4, 0x22, 0x91, 0x48, 0x50, 0xb5, 0x2d, 0x0, 0x80, 0x9b, 0x99, 0x99, 0x79, 0xfb, 0xdc, 0x1, 0xc8, 0x5, 0xa9, 0x44, 0x22, 0xf1, 0xfb, 0x9d, 0x10, 0x0, 0x80, 0x9b, 0x9d, 0x9d, 0xd, 0xea, 0x5, 0xc0, 0xad, 0xfd, 0x43, 0x1a, 0x0, 0xb8, 0xdb, 0x9a, 0xa9, 0x8f, 0xb6, 0xa4, 0x46, 0xa3, 0xa4, 0xb7, 0xd5, 0x37, 0xcf, 0xf3, 0x68, 0x75, 0x75, 0xf5, 0x4c, 0xee, 0x99, 0x1c, 0x80, 0x9c, 0x1e, 0xf7, 0xff, 0x16, 0x8b, 0x45, 0x50, 0x5, 0xa0, 0xb7, 0xb7, 0xb7, 0x85, 0x10, 0xa2, 0x2b, 0xf1, 0x84, 0x10, 0xd4, 0xdf, 0xdf, 0x6f, 0x57, 0x3, 0x80, 0x37, 0x18, 0xc, 0x5, 0x3d, 0x2, 0xa0, 0x69, 0x3a, 0x8b, 0x10, 0xe2, 0x4b, 0x2, 0xc0, 0x18, 0xf3, 0xc1, 0x60, 0x70, 0x47, 0x8f, 0x16, 0x38, 0x3a, 0x3a, 0x5a, 0x93, 0x5b, 0xc3, 0x7f, 0x64, 0x81, 0xba, 0xba, 0x3a, 0x49, 0x8f, 0x0, 0x1a, 0x1a, 0x1a, 0xd4, 0xcd, 0x0, 0x93, 0xc9, 0xa4, 0xcb, 0x21, 0xe8, 0x74, 0x3a, 0xd5, 0x1, 0xa0, 0x69, 0x5a, 0x77, 0x1d, 0x80, 0x31, 0x2e, 0x38, 0x9d, 0x4e, 0xb1, 0x66, 0x1, 0x30, 0xc, 0x23, 0x28, 0x3d, 0x93, 0x9b, 0x1, 0xb9, 0x9a, 0x6, 0x60, 0x36, 0x9b, 0x75, 0xd7, 0x1, 0x4a, 0x21, 0xa8, 0x26, 0x0, 0x94, 0xa, 0x41, 0xb2, 0x0, 0x18, 0x86, 0xc9, 0xe9, 0xd, 0x80, 0x52, 0x8, 0x92, 0x5, 0x60, 0xb1, 0x58, 0x74, 0x67, 0x1, 0xa5, 0x10, 0xa4, 0x4, 0x40, 0x77, 0x43, 0xd0, 0xe1, 0x70, 0xa8, 0x9f, 0x1, 0x14, 0x45, 0x1, 0x45, 0x51, 0x79, 0x3d, 0x1, 0x68, 0x6e, 0x6e, 0x4e, 0xaa, 0x6, 0x80, 0x10, 0x42, 0x6, 0x83, 0x41, 0x37, 0x36, 0x28, 0x15, 0x82, 0x6a, 0x2, 0x0, 0x4d, 0xd3, 0xa9, 0x52, 0xcf, 0x95, 0x0, 0xe8, 0x66, 0xe, 0x98, 0xcd, 0x66, 0xa1, 0x6c, 0x0, 0x7a, 0x5a, 0x8b, 0x59, 0x2c, 0x96, 0x64, 0xcd, 0x2, 0xb8, 0x2b, 0x4, 0xe9, 0xde, 0x2, 0x77, 0x85, 0xa0, 0x9a, 0xb0, 0x40, 0xa9, 0x10, 0xa4, 0x8, 0xc0, 0x64, 0x32, 0xe9, 0x6, 0x40, 0xa9, 0x10, 0x54, 0xaa, 0x3, 0x74, 0xf3, 0x16, 0x70, 0xb9, 0x5c, 0xe5, 0x3, 0xe8, 0xe9, 0xe9, 0x69, 0xd5, 0xc3, 0x66, 0x18, 0x63, 0x5c, 0x68, 0x6a, 0x6a, 0x12, 0xcb, 0x5, 0xa0, 0x9b, 0xd5, 0x38, 0x4d, 0xd3, 0x29, 0x8a, 0xa2, 0xa0, 0x2c, 0x0, 0x18, 0x63, 0x3e, 0x14, 0xa, 0xfd, 0x55, 0xb, 0x21, 0x48, 0xd1, 0x2, 0x7a, 0x59, 0x8d, 0xdf, 0x1b, 0x80, 0x1e, 0x56, 0xe3, 0x84, 0x10, 0x34, 0x30, 0x30, 0x60, 0xbb, 0xeb, 0x77, 0x46, 0x5, 0xef, 0x48, 0xcf, 0x4d, 0xec, 0x8d, 0x99, 0x5, 0xf5, 0xf5, 0xf5, 0xef, 0x46, 0x47, 0x47, 0xb, 0x2e, 0x97, 0xeb, 0xbc, 0x54, 0x8, 0x52, 0x4, 0xc0, 0x30, 0x8c, 0xf4, 0x5c, 0x4, 0x9b, 0x4c, 0xa6, 0xf4, 0xf8, 0xf8, 0xb8, 0xc8, 0xb2, 0x6c, 0x32, 0x9d, 0x4e, 0xff, 0xd4, 0xdd, 0xdd, 0x7d, 0x66, 0x34, 0x1a, 0x8b, 0xd7, 0x3, 0xfd, 0xae, 0x5b, 0x29, 0xb2, 0x57, 0x66, 0xb6, 0xb6, 0xb6, 0xde, 0xc4, 0xe3, 0xf1, 0x6f, 0xae, 0xaf, 0xc1, 0x28, 0x5d, 0x85, 0x79, 0x2, 0xc1, 0x60, 0xb5, 0x5a, 0xa3, 0xa3, 0xa3, 0xa3, 0x45, 0xab, 0xd5, 0x9a, 0x2a, 0x16, 0x8b, 0x8b, 0x6d, 0x6d, 0x6d, 0xef, 0xd5, 0x8a, 0x55, 0xd, 0x20, 0x91, 0x48, 0xbc, 0x3e, 0x38, 0x38, 0xf8, 0xda, 0x6e, 0xb7, 0xf7, 0x5f, 0x5c, 0x5c, 0xd4, 0x7b, 0xbd, 0xde, 0xbc, 0x20, 0x8, 0xcd, 0x85, 0x42, 0x81, 0xfe, 0xf0, 0xae, 0xac, 0x10, 0x98, 0x9b, 0xd5, 0xc5, 0x18, 0x17, 0x59, 0x96, 0x3d, 0x1d, 0x19, 0x19, 0x1, 0x96, 0x65, 0x5, 0x8a, 0xa2, 0x7e, 0x6c, 0x69, 0x69, 0x49, 0x3d, 0x44, 0xb0, 0x2a, 0x0, 0x1f, 0xcc, 0x74, 0x75, 0x41, 0xea, 0xfa, 0x7b, 0x32, 0x99, 0x64, 0x76, 0x77, 0x77, 0x5d, 0xe, 0x87, 0xa3, 0x5f, 0x14, 0xc5, 0x57, 0x57, 0x60, 0x5a, 0x8b, 0xc5, 0xa2, 0xf1, 0xbe, 0x50, 0x6e, 0xa, 0x66, 0x18, 0x26, 0x31, 0x36, 0x36, 0x96, 0x65, 0x59, 0x36, 0x29, 0x49, 0x92, 0xb7, 0xbd, 0xbd, 0xfd, 0x9f, 0x72, 0xda, 0xf9, 0xd1, 0x1, 0xa8, 0x1, 0x93, 0xcf, 0xe7, 0xa9, 0x93, 0x93, 0x13, 0x1b, 0x4d, 0xd3, 0x9f, 0xb, 0x82, 0x60, 0xf5, 0x7a, 0xbd, 0xd9, 0x54, 0x2a, 0xe5, 0xcc, 0x64, 0x32, 0xe, 0xb9, 0x6e, 0xb9, 0x16, 0x8c, 0x31, 0x2e, 0xda, 0x6c, 0xb6, 0xc8, 0xd0, 0xd0, 0x10, 0x65, 0xb3, 0xd9, 0x92, 0x95, 0xa8, 0x6e, 0xc5, 0x0, 0xa8, 0xe9, 0x96, 0x68, 0x34, 0x6a, 0xdd, 0xdf, 0xdf, 0x6f, 0x76, 0xb9, 0x5c, 0x9f, 0x89, 0xa2, 0x58, 0xbf, 0xb8, 0xb8, 0x8, 0x26, 0x93, 0x29, 0x3b, 0x3c, 0x3c, 0x8c, 0xed, 0x76, 0x7b, 0xd2, 0x68, 0x34, 0xfe, 0xd0, 0xd8, 0xd8, 0x98, 0xae, 0xb6, 0xe0, 0x8a, 0x1, 0x50, 0xb, 0xe6, 0xa9, 0x5, 0xbf, 0x9c, 0x97, 0xf3, 0xff, 0xf3, 0x2f, 0x6a, 0x82, 0x7f, 0xf6, 0x4e, 0xca, 0x1b, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+class TestPhysics2DMainLoop : public MainLoop {
+ GDCLASS(TestPhysics2DMainLoop, MainLoop);
+
+ RID circle_img;
+ RID circle_shape;
+ RID space;
+ RID canvas;
+ RID ray;
+ RID ray_query;
+ Transform2D view_xform;
+
+ Vector2 ray_from, ray_to;
+
+ struct BodyShapeData {
+ RID image;
+ RID shape;
+ };
+
+ BodyShapeData body_shape_data[8];
+
+ void _create_body_shape_data() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ // SEGMENT
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 2 * 2);
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 32; j++) {
+ pixels.set(i * 32 * 2 + j * 2 + 0, (j == 0) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, 255);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 2, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_SEGMENT].image = vs->texture_2d_create(image);
+
+ RID segment_shape = ps->segment_shape_create();
+ Rect2 sg(Point2(-16, 0), Point2(16, 0));
+ ps->shape_set_data(segment_shape, sg);
+
+ body_shape_data[PhysicsServer2D::SHAPE_SEGMENT].shape = segment_shape;
+ }
+ // CIRCLE
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 32 * 2);
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < 32; j++) {
+ bool black = Vector2(i - 16, j - 16).length_squared() < 16 * 16;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, (i == 16 || j == 16) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, black ? 255 : 0);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 32, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CIRCLE].image = vs->texture_2d_create(image);
+
+ RID circle_shape = ps->circle_shape_create();
+ ps->shape_set_data(circle_shape, 16);
+
+ body_shape_data[PhysicsServer2D::SHAPE_CIRCLE].shape = circle_shape;
+ }
+
+ // BOX
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 32 * 2);
+ for (int i = 0; i < 32; i++) {
+ for (int j = 0; j < 32; j++) {
+ bool black = i > 0 && i < 31 && j > 0 && j < 31;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, black ? 0 : 255);
+ pixels.set(i * 32 * 2 + j * 2 + 1, 255);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 32, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_RECTANGLE].image = vs->texture_2d_create(image);
+
+ RID rectangle_shape = ps->rectangle_shape_create();
+ ps->shape_set_data(rectangle_shape, Vector2(16, 16));
+
+ body_shape_data[PhysicsServer2D::SHAPE_RECTANGLE].shape = rectangle_shape;
+ }
+
+ // CAPSULE
+
+ {
+ Vector<uint8_t> pixels;
+ pixels.resize(32 * 64 * 2);
+ for (int i = 0; i < 64; i++) {
+ for (int j = 0; j < 32; j++) {
+ int si = i > 48 ? i - 32 : (i < 16 ? i : 16);
+ bool black = Vector2(si - 16, j - 16).length_squared() < 16 * 16;
+
+ pixels.set(i * 32 * 2 + j * 2 + 0, (i == 16 || j == 16 || i == 48) ? 255 : 0);
+ pixels.set(i * 32 * 2 + j * 2 + 1, black ? 255 : 0);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(32, 64, 0, Image::FORMAT_LA8, pixels));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CAPSULE].image = vs->texture_2d_create(image);
+
+ RID capsule_shape = ps->capsule_shape_create();
+ ps->shape_set_data(capsule_shape, Vector2(16, 32));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CAPSULE].shape = capsule_shape;
+ }
+
+ // CONVEX
+
+ {
+ Ref<Image> image = memnew(Image(convex_png));
+
+ body_shape_data[PhysicsServer2D::SHAPE_CONVEX_POLYGON].image = vs->texture_2d_create(image);
+
+ RID convex_polygon_shape = ps->convex_polygon_shape_create();
+
+ Vector<Vector2> arr;
+ Point2 sb(32, 32);
+ arr.push_back(Point2(20, 3) - sb);
+ arr.push_back(Point2(58, 23) - sb);
+ arr.push_back(Point2(55, 54) - sb);
+ arr.push_back(Point2(27, 60) - sb);
+ arr.push_back(Point2(5, 56) - sb);
+ arr.push_back(Point2(4, 20) - sb);
+ arr.push_back(Point2(11, 7) - sb);
+ ps->shape_set_data(convex_polygon_shape, arr);
+
+ body_shape_data[PhysicsServer2D::SHAPE_CONVEX_POLYGON].shape = convex_polygon_shape;
+ }
+ }
+
+ void _do_ray_query() {
+ /*
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+ ps->query_intersection_segment(ray_query,ray_from,ray_to);
+ */
+ }
+
+protected:
+ void input_event(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid()) {
+ if (mb->is_pressed()) {
+ Point2 p = mb->get_position();
+
+ if (mb->get_button_index() == 1) {
+ ray_to = p;
+ _do_ray_query();
+ } else if (mb->get_button_index() == 2) {
+ ray_from = p;
+ _do_ray_query();
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+ Point2 p = mm->get_position();
+
+ if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ ray_to = p;
+ _do_ray_query();
+ } else if (mm->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) {
+ ray_from = p;
+ _do_ray_query();
+ }
+ }
+ }
+
+ RID _add_body(PhysicsServer2D::ShapeType p_shape, const Transform2D &p_xform) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ RID body = ps->body_create();
+ ps->body_add_shape(body, body_shape_data[p_shape].shape);
+ ps->body_set_space(body, space);
+ ps->body_set_continuous_collision_detection_mode(body, PhysicsServer2D::CCD_MODE_CAST_SHAPE);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, p_xform);
+
+ //print_line("add body with xform: "+p_xform);
+ RID sprite = vs->canvas_item_create();
+ vs->canvas_item_set_parent(sprite, canvas);
+ vs->canvas_item_set_transform(sprite, p_xform);
+ Size2 imgsize(5, 5); //vs->texture_get_width(body_shape_data[p_shape].image), vs->texture_get_height(body_shape_data[p_shape].image));
+ vs->canvas_item_add_texture_rect(sprite, Rect2(-imgsize / 2.0, imgsize), body_shape_data[p_shape].image);
+
+ ps->body_set_force_integration_callback(body, callable_mp(this, &TestPhysics2DMainLoop::_body_moved), sprite);
+
+ return body;
+ }
+
+ void _add_world_boundary(const Vector2 &p_normal, real_t p_d) {
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ Array arr;
+ arr.push_back(p_normal);
+ arr.push_back(p_d);
+
+ RID world_boundary = ps->world_boundary_shape_create();
+ ps->shape_set_data(world_boundary, arr);
+
+ RID plane_body = ps->body_create();
+ ps->body_set_mode(plane_body, PhysicsServer2D::BODY_MODE_STATIC);
+ ps->body_set_space(plane_body, space);
+ ps->body_add_shape(plane_body, world_boundary);
+ }
+
+ void _add_concave(const Vector<Vector2> &p_points, const Transform2D &p_xform = Transform2D()) {
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ RID concave = ps->concave_polygon_shape_create();
+ ps->shape_set_data(concave, p_points);
+ RID body = ps->body_create();
+ ps->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC);
+ ps->body_set_space(body, space);
+ ps->body_add_shape(body, concave);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, p_xform);
+
+ RID sprite = vs->canvas_item_create();
+ vs->canvas_item_set_parent(sprite, canvas);
+ vs->canvas_item_set_transform(sprite, p_xform);
+ for (int i = 0; i < p_points.size(); i += 2) {
+ vs->canvas_item_add_line(sprite, p_points[i], p_points[i + 1], Color(0, 0, 0), 2);
+ }
+ }
+
+ void _body_moved(Object *p_state, RID p_sprite) {
+ PhysicsDirectBodyState2D *state = (PhysicsDirectBodyState2D *)p_state;
+ RenderingServer::get_singleton()->canvas_item_set_transform(p_sprite, state->get_transform());
+ }
+
+ void _ray_query_callback(const RID &p_rid, ObjectID p_id, int p_shape, const Vector2 &p_point, const Vector2 &p_normal) {
+ Vector2 ray_end;
+
+ if (p_rid.is_valid()) {
+ ray_end = p_point;
+ } else {
+ ray_end = ray_to;
+ }
+
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ vs->canvas_item_clear(ray);
+ vs->canvas_item_add_line(ray, ray_from, ray_end, p_rid.is_valid() ? Color(0, 1, 0.4) : Color(1, 0.4, 0), 2);
+ if (p_rid.is_valid()) {
+ vs->canvas_item_add_line(ray, ray_end, ray_end + p_normal * 20, p_rid.is_valid() ? Color(0, 1, 0.4) : Color(1, 0.4, 0), 2);
+ }
+ }
+
+ static void _bind_methods() {
+ ClassDB::bind_method(D_METHOD("_ray_query_callback"), &TestPhysics2DMainLoop::_ray_query_callback);
+ }
+
+public:
+ virtual void initialize() override {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ space = ps->space_create();
+ ps->space_set_active(space, true);
+ ps->set_active(true);
+ ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, Vector2(0, 1));
+ ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, 980);
+
+ {
+ RID vp = vs->viewport_create();
+ canvas = vs->canvas_create();
+
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_attach_canvas(vp, canvas);
+ vs->viewport_set_size(vp, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(vp, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(vp, true);
+
+ Transform2D smaller;
+ //smaller.scale(Vector2(0.6,0.6));
+ //smaller.elements[2]=Vector2(100,0);
+
+ //view_xform = smaller;
+ vs->viewport_set_canvas_transform(vp, canvas, view_xform);
+ }
+
+ ray = vs->canvas_item_create();
+ vs->canvas_item_set_parent(ray, canvas);
+ //ray_query = ps->query_create(this,"_ray_query_callback",Variant());
+ //ps->query_intersection(ray_query,space);
+
+ _create_body_shape_data();
+
+ for (int i = 0; i < 32; i++) {
+ PhysicsServer2D::ShapeType types[4] = {
+ PhysicsServer2D::SHAPE_CIRCLE,
+ PhysicsServer2D::SHAPE_CAPSULE,
+ PhysicsServer2D::SHAPE_RECTANGLE,
+ PhysicsServer2D::SHAPE_CONVEX_POLYGON,
+
+ };
+
+ PhysicsServer2D::ShapeType type = types[i % 4];
+ //type=PhysicsServer2D::SHAPE_SEGMENT;
+ _add_body(type, Transform2D(i * 0.8, Point2(152 + i * 40, 100 - 40 * i)));
+ /*
+ if (i==0)
+ ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC);
+ */
+ }
+
+ //RID b= _add_body(PhysicsServer2D::SHAPE_CIRCLE,Transform2D(0,Point2(101,140)));
+ //ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC);
+
+ Point2 prev;
+
+ Vector<Point2> parr;
+ for (int i = 0; i < 30; i++) {
+ Point2 p(i * 60, Math::randf() * 70 + 340);
+ if (i > 0) {
+ parr.push_back(prev);
+ parr.push_back(p);
+ }
+ prev = p;
+ }
+
+ _add_concave(parr);
+ //_add_world_boundary(Vector2(0.0,-1).normalized(),-300);
+ //_add_world_boundary(Vector2(1,0).normalized(),50);
+ //_add_world_boundary(Vector2(-1,0).normalized(),-600);
+ }
+
+ virtual bool process(double p_time) override {
+ return false;
+ }
+ virtual void finalize() override {
+ }
+
+ TestPhysics2DMainLoop() {}
+};
+
+namespace TestPhysics2D {
+
+MainLoop *test() {
+ return memnew(TestPhysics2DMainLoop);
+}
+} // namespace TestPhysics2D
diff --git a/tests/servers/test_physics_2d.h b/tests/servers/test_physics_2d.h
new file mode 100644
index 0000000000..2ae1053a03
--- /dev/null
+++ b/tests/servers/test_physics_2d.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_physics_2d.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_PHYSICS_2D_H
+#define TEST_PHYSICS_2D_H
+
+class MainLoop;
+
+namespace TestPhysics2D {
+
+MainLoop *test();
+}
+
+#endif // TEST_PHYSICS_2D_H
diff --git a/tests/servers/test_physics_3d.cpp b/tests/servers/test_physics_3d.cpp
new file mode 100644
index 0000000000..4e483d628d
--- /dev/null
+++ b/tests/servers/test_physics_3d.cpp
@@ -0,0 +1,410 @@
+/*************************************************************************/
+/* test_physics_3d.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_physics_3d.h"
+
+#include "core/math/convex_hull.h"
+#include "core/math/geometry_3d.h"
+#include "core/os/main_loop.h"
+#include "servers/physics_server_3d.h"
+#include "servers/rendering_server.h"
+
+class TestPhysics3DMainLoop : public MainLoop {
+ GDCLASS(TestPhysics3DMainLoop, MainLoop);
+
+ enum {
+ LINK_COUNT = 20,
+ };
+
+ RID test_cube;
+
+ RID plane;
+ RID sphere;
+ RID light;
+ RID camera;
+ RID mover;
+ RID scenario;
+ RID space;
+
+ RID character;
+
+ real_t ofs_x, ofs_y;
+
+ Point2 joy_direction;
+
+ List<RID> bodies;
+ Map<PhysicsServer3D::ShapeType, RID> type_shape_map;
+ Map<PhysicsServer3D::ShapeType, RID> type_mesh_map;
+
+ void body_changed_transform(Object *p_state, RID p_visual_instance) {
+ PhysicsDirectBodyState3D *state = (PhysicsDirectBodyState3D *)p_state;
+ RenderingServer *vs = RenderingServer::get_singleton();
+ Transform3D t = state->get_transform();
+ vs->instance_set_transform(p_visual_instance, t);
+ }
+
+ bool quit;
+
+protected:
+ RID create_body(PhysicsServer3D::ShapeType p_shape, PhysicsServer3D::BodyMode p_body, const Transform3D p_location, bool p_active_default = true, const Transform3D &p_shape_xform = Transform3D()) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ RID mesh_instance = vs->instance_create2(type_mesh_map[p_shape], scenario);
+ RID body = ps->body_create();
+ ps->body_set_mode(body, p_body);
+ ps->body_set_state(body, PhysicsServer3D::BODY_STATE_SLEEPING, !p_active_default);
+ ps->body_set_space(body, space);
+ ps->body_set_param(body, PhysicsServer3D::BODY_PARAM_BOUNCE, 0.0);
+ //todo set space
+ ps->body_add_shape(body, type_shape_map[p_shape]);
+ ps->body_set_force_integration_callback(body, callable_mp(this, &TestPhysics3DMainLoop::body_changed_transform), mesh_instance);
+
+ ps->body_set_state(body, PhysicsServer3D::BODY_STATE_TRANSFORM, p_location);
+ bodies.push_back(body);
+
+ if (p_body == PhysicsServer3D::BODY_MODE_STATIC) {
+ vs->instance_set_transform(mesh_instance, p_location);
+ }
+ return body;
+ }
+
+ RID create_world_boundary(const Plane &p_plane) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ RID world_boundary_shape = ps->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY);
+ ps->shape_set_data(world_boundary_shape, p_plane);
+
+ RID b = ps->body_create();
+ ps->body_set_mode(b, PhysicsServer3D::BODY_MODE_STATIC);
+
+ ps->body_set_space(b, space);
+ ps->body_add_shape(b, world_boundary_shape);
+ return b;
+ }
+
+ void configure_body(RID p_body, real_t p_mass, real_t p_friction, real_t p_bounce) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_MASS, p_mass);
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_FRICTION, p_friction);
+ ps->body_set_param(p_body, PhysicsServer3D::BODY_PARAM_BOUNCE, p_bounce);
+ }
+
+ void initialize_shapes() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ /* SPHERE SHAPE */
+ RID sphere_mesh = vs->make_sphere_mesh(10, 20, 0.5);
+ type_mesh_map[PhysicsServer3D::SHAPE_SPHERE] = sphere_mesh;
+
+ RID sphere_shape = ps->shape_create(PhysicsServer3D::SHAPE_SPHERE);
+ ps->shape_set_data(sphere_shape, 0.5);
+ type_shape_map[PhysicsServer3D::SHAPE_SPHERE] = sphere_shape;
+
+ /* BOX SHAPE */
+
+ Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(0.5, 0.5, 0.5));
+ RID box_mesh = vs->mesh_create();
+ Geometry3D::MeshData box_data = Geometry3D::build_convex_mesh(box_planes);
+ vs->mesh_add_surface_from_mesh_data(box_mesh, box_data);
+ type_mesh_map[PhysicsServer3D::SHAPE_BOX] = box_mesh;
+
+ RID box_shape = ps->shape_create(PhysicsServer3D::SHAPE_BOX);
+ ps->shape_set_data(box_shape, Vector3(0.5, 0.5, 0.5));
+ type_shape_map[PhysicsServer3D::SHAPE_BOX] = box_shape;
+
+ /* CAPSULE SHAPE */
+
+ Vector<Plane> capsule_planes = Geometry3D::build_capsule_planes(0.5, 0.7, 12, Vector3::AXIS_Z);
+
+ RID capsule_mesh = vs->mesh_create();
+ Geometry3D::MeshData capsule_data = Geometry3D::build_convex_mesh(capsule_planes);
+ vs->mesh_add_surface_from_mesh_data(capsule_mesh, capsule_data);
+
+ type_mesh_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_mesh;
+
+ RID capsule_shape = ps->shape_create(PhysicsServer3D::SHAPE_CAPSULE);
+ Dictionary capsule_params;
+ capsule_params["radius"] = 0.5;
+ capsule_params["height"] = 1.4;
+ ps->shape_set_data(capsule_shape, capsule_params);
+ type_shape_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_shape;
+
+ /* CONVEX SHAPE */
+
+ Vector<Plane> convex_planes = Geometry3D::build_cylinder_planes(0.5, 0.7, 5, Vector3::AXIS_Z);
+
+ RID convex_mesh = vs->mesh_create();
+ Geometry3D::MeshData convex_data = Geometry3D::build_convex_mesh(convex_planes);
+ ConvexHullComputer::convex_hull(convex_data.vertices, convex_data);
+ vs->mesh_add_surface_from_mesh_data(convex_mesh, convex_data);
+
+ type_mesh_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_mesh;
+
+ RID convex_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON);
+ ps->shape_set_data(convex_shape, convex_data.vertices);
+ type_shape_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_shape;
+ }
+
+ void make_trimesh(Vector<Vector3> p_faces, const Transform3D &p_xform = Transform3D()) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ RID trimesh_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON);
+ Dictionary trimesh_params;
+ trimesh_params["faces"] = p_faces;
+ trimesh_params["backface_collision"] = false;
+ ps->shape_set_data(trimesh_shape, trimesh_params);
+ Vector<Vector3> normals; // for drawing
+ for (int i = 0; i < p_faces.size() / 3; i++) {
+ Plane p(p_faces[i * 3 + 0], p_faces[i * 3 + 1], p_faces[i * 3 + 2]);
+ normals.push_back(p.normal);
+ normals.push_back(p.normal);
+ normals.push_back(p.normal);
+ }
+
+ RID trimesh_mesh = vs->mesh_create();
+ Array d;
+ d.resize(RS::ARRAY_MAX);
+ d[RS::ARRAY_VERTEX] = p_faces;
+ d[RS::ARRAY_NORMAL] = normals;
+ vs->mesh_add_surface_from_arrays(trimesh_mesh, RS::PRIMITIVE_TRIANGLES, d);
+
+ RID triins = vs->instance_create2(trimesh_mesh, scenario);
+
+ RID tribody = ps->body_create();
+ ps->body_set_mode(tribody, PhysicsServer3D::BODY_MODE_STATIC);
+ ps->body_set_space(tribody, space);
+ //todo set space
+ ps->body_add_shape(tribody, trimesh_shape);
+ Transform3D tritrans = p_xform;
+ ps->body_set_state(tribody, PhysicsServer3D::BODY_STATE_TRANSFORM, tritrans);
+ vs->instance_set_transform(triins, tritrans);
+ }
+
+ void make_grid(int p_width, int p_height, real_t p_cellsize, real_t p_cellheight, const Transform3D &p_xform = Transform3D()) {
+ Vector<Vector<real_t>> grid;
+
+ grid.resize(p_width);
+
+ for (int i = 0; i < p_width; i++) {
+ grid.write[i].resize(p_height);
+
+ for (int j = 0; j < p_height; j++) {
+ grid.write[i].write[j] = 1.0 + Math::random(-p_cellheight, p_cellheight);
+ }
+ }
+
+ Vector<Vector3> faces;
+
+ for (int i = 1; i < p_width; i++) {
+ for (int j = 1; j < p_height; j++) {
+#define MAKE_VERTEX(m_x, m_z) \
+ faces.push_back(Vector3((m_x - p_width / 2) * p_cellsize, grid[m_x][m_z], (m_z - p_height / 2) * p_cellsize))
+
+ MAKE_VERTEX(i, j - 1);
+ MAKE_VERTEX(i, j);
+ MAKE_VERTEX(i - 1, j);
+
+ MAKE_VERTEX(i - 1, j - 1);
+ MAKE_VERTEX(i, j - 1);
+ MAKE_VERTEX(i - 1, j);
+ }
+ }
+
+ make_trimesh(faces, p_xform);
+ }
+
+public:
+ virtual void input_event(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid() && mm->get_button_mask() & 4) {
+ ofs_y -= mm->get_relative().y / 200.0;
+ ofs_x += mm->get_relative().x / 200.0;
+ }
+
+ if (mm.is_valid() && mm->get_button_mask() & 1) {
+ real_t y = -mm->get_relative().y / 20.0;
+ real_t x = mm->get_relative().x / 20.0;
+
+ if (mover.is_valid()) {
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ Transform3D t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM);
+ t.origin += Vector3(x, y, 0);
+
+ ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t);
+ }
+ }
+ }
+
+ virtual void request_quit() {
+ quit = true;
+ }
+ virtual void initialize() override {
+ ofs_x = ofs_y = 0;
+ initialize_shapes();
+
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ space = ps->space_create();
+ ps->space_set_active(space, true);
+
+ RenderingServer *vs = RenderingServer::get_singleton();
+
+ /* LIGHT */
+ RID lightaux = vs->directional_light_create();
+ scenario = vs->scenario_create();
+ vs->light_set_shadow(lightaux, true);
+ light = vs->instance_create2(lightaux, scenario);
+ Transform3D t;
+ t.rotate(Vector3(1.0, 0, 0), 0.6);
+ vs->instance_set_transform(light, t);
+
+ /* CAMERA */
+
+ camera = vs->camera_create();
+
+ RID viewport = vs->viewport_create();
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_set_size(viewport, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(viewport, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(viewport, true);
+ vs->viewport_attach_camera(viewport, camera);
+ vs->viewport_set_scenario(viewport, scenario);
+
+ vs->camera_set_perspective(camera, 60, 0.1, 40.0);
+ vs->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 9, 12)));
+
+ Transform3D gxf;
+ gxf.basis.scale(Vector3(1.4, 0.4, 1.4));
+ gxf.origin = Vector3(-2, 1, -2);
+ make_grid(5, 5, 2.5, 1, gxf);
+ test_fall();
+ quit = false;
+ }
+ virtual bool physics_process(double p_time) override {
+ if (mover.is_valid()) {
+ static real_t joy_speed = 10;
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+ Transform3D t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM);
+ t.origin += Vector3(joy_speed * joy_direction.x * p_time, -joy_speed * joy_direction.y * p_time, 0);
+ ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t);
+ };
+
+ Transform3D cameratr;
+ cameratr.rotate(Vector3(0, 1, 0), ofs_x);
+ cameratr.rotate(Vector3(1, 0, 0), -ofs_y);
+ cameratr.translate(Vector3(0, 2, 8));
+ RenderingServer *vs = RenderingServer::get_singleton();
+ vs->camera_set_transform(camera, cameratr);
+
+ return quit;
+ }
+ virtual void finalize() override {
+ }
+
+ void test_joint() {
+ }
+
+ void test_hinge() {
+ }
+
+ void test_character() {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
+
+ Vector<Plane> capsule_planes = Geometry3D::build_capsule_planes(0.5, 1, 12, 5, Vector3::AXIS_Y);
+
+ RID capsule_mesh = vs->mesh_create();
+ Geometry3D::MeshData capsule_data = Geometry3D::build_convex_mesh(capsule_planes);
+ vs->mesh_add_surface_from_mesh_data(capsule_mesh, capsule_data);
+ type_mesh_map[PhysicsServer3D::SHAPE_CAPSULE] = capsule_mesh;
+
+ RID capsule_shape = ps->shape_create(PhysicsServer3D::SHAPE_CAPSULE);
+ Dictionary capsule_params;
+ capsule_params["radius"] = 0.5;
+ capsule_params["height"] = 1;
+ Transform3D shape_xform;
+ shape_xform.rotate(Vector3(1, 0, 0), Math_PI / 2.0);
+ //shape_xform.origin=Vector3(1,1,1);
+ ps->shape_set_data(capsule_shape, capsule_params);
+
+ RID mesh_instance = vs->instance_create2(capsule_mesh, scenario);
+ character = ps->body_create();
+ ps->body_set_mode(character, PhysicsServer3D::BODY_MODE_DYNAMIC_LINEAR);
+ ps->body_set_space(character, space);
+ //todo add space
+ ps->body_add_shape(character, capsule_shape);
+ ps->body_set_force_integration_callback(character, callable_mp(this, &TestPhysics3DMainLoop::body_changed_transform), mesh_instance);
+
+ ps->body_set_state(character, PhysicsServer3D::BODY_STATE_TRANSFORM, Transform3D(Basis(), Vector3(-2, 5, -2)));
+ bodies.push_back(character);
+ }
+
+ void test_fall() {
+ for (int i = 0; i < 35; i++) {
+ static const PhysicsServer3D::ShapeType shape_idx[] = {
+ PhysicsServer3D::SHAPE_CAPSULE,
+ PhysicsServer3D::SHAPE_BOX,
+ PhysicsServer3D::SHAPE_SPHERE,
+ PhysicsServer3D::SHAPE_CONVEX_POLYGON
+ };
+
+ PhysicsServer3D::ShapeType type = shape_idx[i % 4];
+
+ Transform3D t;
+
+ t.origin = Vector3(0.0 * i, 3.5 + 1.1 * i, 0.7 + 0.0 * i);
+ t.basis.rotate(Vector3(0.2, -1, 0), Math_PI / 2 * 0.6);
+
+ create_body(type, PhysicsServer3D::BODY_MODE_DYNAMIC, t);
+ }
+
+ create_world_boundary(Plane(Vector3(0, 1, 0), -1));
+ }
+
+ void test_activate() {
+ create_body(PhysicsServer3D::SHAPE_BOX, PhysicsServer3D::BODY_MODE_DYNAMIC, Transform3D(Basis(), Vector3(0, 2, 0)), true);
+ create_world_boundary(Plane(Vector3(0, 1, 0), -1));
+ }
+
+ virtual bool process(double p_time) override {
+ return false;
+ }
+
+ TestPhysics3DMainLoop() {
+ }
+};
+
+namespace TestPhysics3D {
+
+MainLoop *test() {
+ return memnew(TestPhysics3DMainLoop);
+}
+} // namespace TestPhysics3D
diff --git a/tests/servers/test_physics_3d.h b/tests/servers/test_physics_3d.h
new file mode 100644
index 0000000000..b86327cdb4
--- /dev/null
+++ b/tests/servers/test_physics_3d.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_physics_3d.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_PHYSICS_H
+#define TEST_PHYSICS_H
+
+class MainLoop;
+
+namespace TestPhysics3D {
+
+MainLoop *test();
+}
+
+#endif
diff --git a/tests/servers/test_render.cpp b/tests/servers/test_render.cpp
new file mode 100644
index 0000000000..183f049238
--- /dev/null
+++ b/tests/servers/test_render.cpp
@@ -0,0 +1,232 @@
+/*************************************************************************/
+/* test_render.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_render.h"
+
+#include "core/math/convex_hull.h"
+#include "core/os/main_loop.h"
+#include "servers/rendering_server.h"
+
+#define OBJECT_COUNT 50
+
+namespace TestRender {
+
+class TestMainLoop : public MainLoop {
+ RID test_cube;
+ RID instance;
+ RID camera;
+ RID viewport;
+ RID light;
+ RID scenario;
+
+ struct InstanceInfo {
+ RID instance;
+ Transform3D base;
+ Vector3 rot_axis;
+ };
+
+ List<InstanceInfo> instances;
+
+ float ofs;
+ bool quit;
+
+protected:
+public:
+ virtual void input_event(const Ref<InputEvent> &p_event) {
+ if (p_event->is_pressed()) {
+ quit = true;
+ }
+ }
+
+ virtual void init() {
+ print_line("INITIALIZING TEST RENDER");
+ RenderingServer *vs = RenderingServer::get_singleton();
+ test_cube = vs->get_test_cube();
+ scenario = vs->scenario_create();
+
+ Vector<Vector3> vts;
+
+ /*
+ Vector<Plane> sp = Geometry3D::build_sphere_planes(2,5,5);
+ Geometry3D::MeshData md2 = Geometry3D::build_convex_mesh(sp);
+ vts=md2.vertices;
+*/
+ /*
+
+ static const int s = 20;
+ for(int i=0;i<s;i++) {
+ Basis rot(Vector3(0,1,0),i*Math_PI/s);
+
+ for(int j=0;j<s;j++) {
+ Vector3 v;
+ v.x=Math::sin(j*Math_PI*2/s);
+ v.y=Math::cos(j*Math_PI*2/s);
+
+ vts.push_back( rot.xform(v*2 ) );
+ }
+ }*/
+ /*for(int i=0;i<100;i++) {
+ vts.push_back( Vector3(Math::randf()*2-1.0,Math::randf()*2-1.0,Math::randf()*2-1.0).normalized()*2);
+ }*/
+ /*
+ vts.push_back(Vector3(0,0,1));
+ vts.push_back(Vector3(0,0,-1));
+ vts.push_back(Vector3(0,1,0));
+ vts.push_back(Vector3(0,-1,0));
+ vts.push_back(Vector3(1,0,0));
+ vts.push_back(Vector3(-1,0,0));*/
+
+ vts.push_back(Vector3(1, 1, 1));
+ vts.push_back(Vector3(1, -1, 1));
+ vts.push_back(Vector3(-1, 1, 1));
+ vts.push_back(Vector3(-1, -1, 1));
+ vts.push_back(Vector3(1, 1, -1));
+ vts.push_back(Vector3(1, -1, -1));
+ vts.push_back(Vector3(-1, 1, -1));
+ vts.push_back(Vector3(-1, -1, -1));
+
+ Geometry3D::MeshData md;
+ Error err = ConvexHullComputer::convex_hull(vts, md);
+ print_line("ERR: " + itos(err));
+ test_cube = vs->mesh_create();
+ vs->mesh_add_surface_from_mesh_data(test_cube, md);
+ //vs->scenario_set_debug(scenario,RS::SCENARIO_DEBUG_WIREFRAME);
+
+ /*
+ RID sm = vs->shader_create();
+ //vs->shader_set_fragment_code(sm,"OUT_ALPHA=mod(TIME,1);");
+ //vs->shader_set_vertex_code(sm,"OUT_VERTEX=IN_VERTEX*mod(TIME,1);");
+ vs->shader_set_fragment_code(sm,"OUT_DIFFUSE=vec3(1,0,1);OUT_GLOW=abs(sin(TIME));");
+ RID tcmat = vs->mesh_surface_get_material(test_cube,0);
+ vs->material_set_shader(tcmat,sm);
+ */
+
+ List<String> cmdline = OS::get_singleton()->get_cmdline_args();
+ int object_count = OBJECT_COUNT;
+ if (cmdline.size() > 0 && cmdline[cmdline.size() - 1].to_int()) {
+ object_count = cmdline[cmdline.size() - 1].to_int();
+ };
+
+ for (int i = 0; i < object_count; i++) {
+ InstanceInfo ii;
+
+ ii.instance = vs->instance_create2(test_cube, scenario);
+
+ ii.base.translate(Math::random(-20, 20), Math::random(-20, 20), Math::random(-20, 18));
+ ii.base.rotate(Vector3(0, 1, 0), Math::randf() * Math_PI);
+ ii.base.rotate(Vector3(1, 0, 0), Math::randf() * Math_PI);
+ vs->instance_set_transform(ii.instance, ii.base);
+
+ ii.rot_axis = Vector3(Math::random(-1, 1), Math::random(-1, 1), Math::random(-1, 1)).normalized();
+
+ instances.push_back(ii);
+ }
+
+ camera = vs->camera_create();
+
+ // vs->camera_set_perspective( camera, 60.0,0.1, 100.0 );
+
+ viewport = vs->viewport_create();
+ Size2i screen_size = DisplayServer::get_singleton()->window_get_size();
+ vs->viewport_set_size(viewport, screen_size.x, screen_size.y);
+ vs->viewport_attach_to_screen(viewport, Rect2(Vector2(), screen_size));
+ vs->viewport_set_active(viewport, true);
+ vs->viewport_attach_camera(viewport, camera);
+ vs->viewport_set_scenario(viewport, scenario);
+ vs->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 3, 30)));
+ vs->camera_set_perspective(camera, 60, 0.1, 1000);
+
+ /*
+ RID lightaux = vs->light_create( RenderingServer::LIGHT_OMNI );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_RADIUS, 80 );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_ATTENUATION, 1 );
+ vs->light_set_var( lightaux, RenderingServer::LIGHT_VAR_ENERGY, 1.5 );
+ light = vs->instance_create( lightaux );
+ */
+ RID lightaux;
+
+ lightaux = vs->directional_light_create();
+ //vs->light_set_color( lightaux, RenderingServer::LIGHT_COLOR_AMBIENT, Color(0.0,0.0,0.0) );
+ vs->light_set_color(lightaux, Color(1.0, 1.0, 1.0));
+ //vs->light_set_shadow( lightaux, true );
+ light = vs->instance_create2(lightaux, scenario);
+ Transform3D lla;
+ //lla.set_look_at(Vector3(),Vector3(1, -1, 1));
+ lla.basis = Basis::looking_at(Vector3(0.0, -0.836026, -0.548690));
+
+ vs->instance_set_transform(light, lla);
+
+ lightaux = vs->omni_light_create();
+ //vs->light_set_color( lightaux, RenderingServer::LIGHT_COLOR_AMBIENT, Color(0.0,0.0,1.0) );
+ vs->light_set_color(lightaux, Color(1.0, 1.0, 0.0));
+ vs->light_set_param(lightaux, RenderingServer::LIGHT_PARAM_RANGE, 4);
+ vs->light_set_param(lightaux, RenderingServer::LIGHT_PARAM_ENERGY, 8);
+ //vs->light_set_shadow( lightaux, true );
+ //light = vs->instance_create( lightaux );
+
+ ofs = 0;
+ quit = false;
+ }
+ virtual bool iteration(double p_time) {
+ RenderingServer *vs = RenderingServer::get_singleton();
+ //Transform3D t;
+ //t.rotate(Vector3(0, 1, 0), ofs);
+ //t.translate(Vector3(0,0,20 ));
+ //vs->camera_set_transform(camera, t);
+
+ ofs += p_time * 0.05;
+
+ //return quit;
+
+ for (const InstanceInfo &E : instances) {
+ Transform3D pre(Basis(E.rot_axis, ofs), Vector3());
+ vs->instance_set_transform(E.instance, pre * E.base);
+ /*
+ if( !E->next() ) {
+ vs->free( E.instance );
+ instances.erase(E );
+ }*/
+ }
+
+ return quit;
+ }
+
+ virtual bool idle(double p_time) {
+ return quit;
+ }
+
+ virtual void finish() {
+ }
+};
+
+MainLoop *test() {
+ return memnew(TestMainLoop);
+}
+} // namespace TestRender
diff --git a/tests/servers/test_render.h b/tests/servers/test_render.h
new file mode 100644
index 0000000000..1d773cb347
--- /dev/null
+++ b/tests/servers/test_render.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_render.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_RENDER_H
+#define TEST_RENDER_H
+
+class MainLoop;
+
+namespace TestRender {
+
+MainLoop *test();
+}
+
+#endif // TEST_RENDER_H
diff --git a/tests/servers/test_shader_lang.cpp b/tests/servers/test_shader_lang.cpp
new file mode 100644
index 0000000000..0591bf6adf
--- /dev/null
+++ b/tests/servers/test_shader_lang.cpp
@@ -0,0 +1,360 @@
+/*************************************************************************/
+/* test_shader_lang.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_shader_lang.h"
+
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "servers/rendering/shader_language.h"
+
+typedef ShaderLanguage SL;
+
+namespace TestShaderLang {
+
+static String _mktab(int p_level) {
+ String tb;
+ for (int i = 0; i < p_level; i++) {
+ tb += "\t";
+ }
+
+ return tb;
+}
+
+static String _typestr(SL::DataType p_type) {
+ return ShaderLanguage::get_datatype_name(p_type);
+}
+
+static String _prestr(SL::DataPrecision p_pres) {
+ switch (p_pres) {
+ case SL::PRECISION_LOWP:
+ return "lowp ";
+ case SL::PRECISION_MEDIUMP:
+ return "mediump ";
+ case SL::PRECISION_HIGHP:
+ return "highp ";
+ case SL::PRECISION_DEFAULT:
+ return "";
+ }
+ return "";
+}
+
+static String _opstr(SL::Operator p_op) {
+ return ShaderLanguage::get_operator_text(p_op);
+}
+
+static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNode::Value> &p_values) {
+ switch (p_type) {
+ case SL::TYPE_BOOL:
+ return p_values[0].boolean ? "true" : "false";
+ case SL::TYPE_BVEC2:
+ return String() + "bvec2(" + (p_values[0].boolean ? "true" : "false") + (p_values[1].boolean ? "true" : "false") + ")";
+ case SL::TYPE_BVEC3:
+ return String() + "bvec3(" + (p_values[0].boolean ? "true" : "false") + "," + (p_values[1].boolean ? "true" : "false") + "," + (p_values[2].boolean ? "true" : "false") + ")";
+ case SL::TYPE_BVEC4:
+ return String() + "bvec4(" + (p_values[0].boolean ? "true" : "false") + "," + (p_values[1].boolean ? "true" : "false") + "," + (p_values[2].boolean ? "true" : "false") + "," + (p_values[3].boolean ? "true" : "false") + ")";
+ case SL::TYPE_INT:
+ return rtos(p_values[0].sint);
+ case SL::TYPE_IVEC2:
+ return String() + "ivec2(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + ")";
+ case SL::TYPE_IVEC3:
+ return String() + "ivec3(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + "," + rtos(p_values[2].sint) + ")";
+ case SL::TYPE_IVEC4:
+ return String() + "ivec4(" + rtos(p_values[0].sint) + "," + rtos(p_values[1].sint) + "," + rtos(p_values[2].sint) + "," + rtos(p_values[3].sint) + ")";
+ case SL::TYPE_UINT:
+ return rtos(p_values[0].real);
+ case SL::TYPE_UVEC2:
+ return String() + "uvec2(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + ")";
+ case SL::TYPE_UVEC3:
+ return String() + "uvec3(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + ")";
+ case SL::TYPE_UVEC4:
+ return String() + "uvec4(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + "," + rtos(p_values[3].real) + ")";
+ case SL::TYPE_FLOAT:
+ return rtos(p_values[0].real);
+ case SL::TYPE_VEC2:
+ return String() + "vec2(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + ")";
+ case SL::TYPE_VEC3:
+ return String() + "vec3(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + ")";
+ case SL::TYPE_VEC4:
+ return String() + "vec4(" + rtos(p_values[0].real) + "," + rtos(p_values[1].real) + "," + rtos(p_values[2].real) + "," + rtos(p_values[3].real) + ")";
+ default:
+ ERR_FAIL_V(String());
+ }
+}
+
+static String dump_node_code(SL::Node *p_node, int p_level) {
+ String code;
+
+ switch (p_node->type) {
+ case SL::Node::TYPE_SHADER: {
+ SL::ShaderNode *pnode = (SL::ShaderNode *)p_node;
+
+ for (const KeyValue<StringName, SL::ShaderNode::Uniform> &E : pnode->uniforms) {
+ String ucode = "uniform ";
+ ucode += _prestr(E.value.precision);
+ ucode += _typestr(E.value.type);
+ ucode += " " + String(E.key);
+ if (E.value.array_size > 0) {
+ ucode += "[";
+ ucode += itos(E.value.array_size);
+ ucode += "]";
+ } else {
+ if (E.value.default_value.size()) {
+ ucode += " = " + get_constant_text(E.value.type, E.value.default_value);
+ }
+
+ static const char *hint_name[SL::ShaderNode::Uniform::HINT_MAX] = {
+ "",
+ "color",
+ "range",
+ "albedo",
+ "normal",
+ "black",
+ "white"
+ };
+
+ if (E.value.hint) {
+ ucode += " : " + String(hint_name[E.value.hint]);
+ }
+ }
+
+ code += ucode + "\n";
+ }
+
+ for (const KeyValue<StringName, SL::ShaderNode::Varying> &E : pnode->varyings) {
+ String vcode = "varying ";
+ vcode += _prestr(E.value.precision);
+ vcode += _typestr(E.value.type);
+ vcode += " " + String(E.key);
+
+ code += vcode + "\n";
+ }
+ for (int i = 0; i < pnode->functions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->functions[i].function;
+
+ String header;
+ header = _typestr(fnode->return_type) + " " + fnode->name + "(";
+ for (int j = 0; j < fnode->arguments.size(); j++) {
+ if (j > 0) {
+ header += ", ";
+ }
+ header += _prestr(fnode->arguments[j].precision) + _typestr(fnode->arguments[j].type) + " " + fnode->arguments[j].name;
+ }
+
+ header += ")\n";
+ code += header;
+ code += dump_node_code(fnode->body, p_level + 1);
+ }
+
+ //code+=dump_node_code(pnode->body,p_level);
+ } break;
+ case SL::Node::TYPE_STRUCT: {
+ } break;
+ case SL::Node::TYPE_FUNCTION: {
+ } break;
+ case SL::Node::TYPE_BLOCK: {
+ SL::BlockNode *bnode = (SL::BlockNode *)p_node;
+
+ //variables
+ code += _mktab(p_level - 1) + "{\n";
+ for (const KeyValue<StringName, SL::BlockNode::Variable> &E : bnode->variables) {
+ code += _mktab(p_level) + _prestr(E.value.precision) + _typestr(E.value.type) + " " + E.key + ";\n";
+ }
+
+ for (int i = 0; i < bnode->statements.size(); i++) {
+ String scode = dump_node_code(bnode->statements[i], p_level);
+
+ if (bnode->statements[i]->type == SL::Node::TYPE_CONTROL_FLOW) {
+ code += scode; //use directly
+ } else {
+ code += _mktab(p_level) + scode + ";\n";
+ }
+ }
+ code += _mktab(p_level - 1) + "}\n";
+
+ } break;
+ case SL::Node::TYPE_VARIABLE: {
+ SL::VariableNode *vnode = (SL::VariableNode *)p_node;
+ code = vnode->name;
+
+ } break;
+ case SL::Node::TYPE_VARIABLE_DECLARATION: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_ARRAY: {
+ SL::ArrayNode *vnode = (SL::ArrayNode *)p_node;
+ code = vnode->name;
+ } break;
+ case SL::Node::TYPE_ARRAY_DECLARATION: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_ARRAY_CONSTRUCT: {
+ // FIXME: Implement
+ } break;
+ case SL::Node::TYPE_CONSTANT: {
+ SL::ConstantNode *cnode = (SL::ConstantNode *)p_node;
+ return get_constant_text(cnode->datatype, cnode->values);
+
+ } break;
+ case SL::Node::TYPE_OPERATOR: {
+ SL::OperatorNode *onode = (SL::OperatorNode *)p_node;
+
+ switch (onode->op) {
+ case SL::OP_ASSIGN:
+ case SL::OP_ASSIGN_ADD:
+ case SL::OP_ASSIGN_SUB:
+ case SL::OP_ASSIGN_MUL:
+ case SL::OP_ASSIGN_DIV:
+ case SL::OP_ASSIGN_SHIFT_LEFT:
+ case SL::OP_ASSIGN_SHIFT_RIGHT:
+ case SL::OP_ASSIGN_MOD:
+ case SL::OP_ASSIGN_BIT_AND:
+ case SL::OP_ASSIGN_BIT_OR:
+ case SL::OP_ASSIGN_BIT_XOR:
+ code = dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op) + dump_node_code(onode->arguments[1], p_level);
+ break;
+ case SL::OP_BIT_INVERT:
+ case SL::OP_NEGATE:
+ case SL::OP_NOT:
+ case SL::OP_DECREMENT:
+ case SL::OP_INCREMENT:
+ code = _opstr(onode->op) + dump_node_code(onode->arguments[0], p_level);
+ break;
+ case SL::OP_POST_DECREMENT:
+ case SL::OP_POST_INCREMENT:
+ code = dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op);
+ break;
+ case SL::OP_CALL:
+ case SL::OP_CONSTRUCT:
+ code = dump_node_code(onode->arguments[0], p_level) + "(";
+ for (int i = 1; i < onode->arguments.size(); i++) {
+ if (i > 1) {
+ code += ", ";
+ }
+ code += dump_node_code(onode->arguments[i], p_level);
+ }
+ code += ")";
+ break;
+ default: {
+ code = "(" + dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op) + dump_node_code(onode->arguments[1], p_level) + ")";
+ break;
+ }
+ }
+
+ } break;
+ case SL::Node::TYPE_CONTROL_FLOW: {
+ SL::ControlFlowNode *cfnode = (SL::ControlFlowNode *)p_node;
+ if (cfnode->flow_op == SL::FLOW_OP_IF) {
+ code += _mktab(p_level) + "if (" + dump_node_code(cfnode->expressions[0], p_level) + ")\n";
+ code += dump_node_code(cfnode->blocks[0], p_level + 1);
+ if (cfnode->blocks.size() == 2) {
+ code += _mktab(p_level) + "else\n";
+ code += dump_node_code(cfnode->blocks[1], p_level + 1);
+ }
+
+ } else if (cfnode->flow_op == SL::FLOW_OP_RETURN) {
+ if (cfnode->blocks.size()) {
+ code = "return " + dump_node_code(cfnode->blocks[0], p_level);
+ } else {
+ code = "return";
+ }
+ }
+
+ } break;
+ case SL::Node::TYPE_MEMBER: {
+ SL::MemberNode *mnode = (SL::MemberNode *)p_node;
+ code = dump_node_code(mnode->owner, p_level) + "." + mnode->name;
+
+ } break;
+ }
+
+ return code;
+}
+
+static Error recreate_code(void *p_str, SL::ShaderNode *p_program) {
+ String *str = (String *)p_str;
+
+ *str = dump_node_code(p_program, 0);
+
+ return OK;
+}
+
+MainLoop *test() {
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.is_empty()) {
+ //try editor!
+ print_line("usage: godot -test shader_lang <shader>");
+ return nullptr;
+ }
+
+ String test = cmdlargs.back()->get();
+
+ FileAccess *fa = FileAccess::open(test, FileAccess::READ);
+
+ if (!fa) {
+ ERR_FAIL_V(nullptr);
+ }
+
+ String code;
+
+ while (true) {
+ char32_t c = fa->get_8();
+ if (fa->eof_reached()) {
+ break;
+ }
+ code += c;
+ }
+
+ SL sl;
+ print_line("tokens:\n\n" + sl.token_debug(code));
+
+ Map<StringName, SL::FunctionInfo> dt;
+ dt["fragment"].built_ins["ALBEDO"] = SL::TYPE_VEC3;
+ dt["fragment"].can_discard = true;
+
+ Vector<StringName> rm;
+ rm.push_back("popo");
+ Set<String> types;
+ types.insert("spatial");
+
+ Error err = sl.compile(code, dt, rm, ShaderLanguage::VaryingFunctionNames(), types, nullptr);
+
+ if (err) {
+ print_line("Error at line: " + rtos(sl.get_error_line()) + ": " + sl.get_error_text());
+ return nullptr;
+ } else {
+ String code2;
+ recreate_code(&code2, sl.get_shader());
+ print_line("code:\n\n" + code2);
+ }
+
+ return nullptr;
+}
+} // namespace TestShaderLang
diff --git a/tests/servers/test_shader_lang.h b/tests/servers/test_shader_lang.h
new file mode 100644
index 0000000000..de7ec002b6
--- /dev/null
+++ b/tests/servers/test_shader_lang.h
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* test_shader_lang.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_SHADER_LANG_H
+#define TEST_SHADER_LANG_H
+
+class MainLoop;
+
+namespace TestShaderLang {
+
+MainLoop *test();
+}
+
+#endif // TEST_SHADER_LANG_H
diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h
new file mode 100644
index 0000000000..4edffe3711
--- /dev/null
+++ b/tests/servers/test_text_server.h
@@ -0,0 +1,296 @@
+/*************************************************************************/
+/* test_text_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef TOOLS_ENABLED
+
+#ifndef TEST_TEXT_SERVER_H
+#define TEST_TEXT_SERVER_H
+
+#include "editor/builtin_fonts.gen.h"
+#include "servers/text_server.h"
+#include "tests/test_macros.h"
+
+namespace TestTextServer {
+
+TEST_SUITE("[[TextServer]") {
+ TEST_CASE("[TextServer] Init, font loading and shaping") {
+ SUBCASE("[TextServer] Loading fonts") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ RID font = ts->create_font();
+ ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ TEST_FAIL_COND(font == RID(), "Loading font failed.");
+ ts->free(font);
+ }
+ }
+
+ SUBCASE("[TextServer] Text layout: Font fallback") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
+
+ Vector<RID> font;
+ font.push_back(font1);
+ font.push_back(font2);
+
+ String test = U"คนอ้วน khon uan ראה";
+ // 6^ 17^
+
+ RID ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+ int gl_size = ts->shaped_text_get_glyph_count(ctx);
+ TEST_FAIL_COND(gl_size == 0, "Shaping failed");
+ for (int j = 0; j < gl_size; j++) {
+ if (glyphs[j].start < 6) {
+ TEST_FAIL_COND(glyphs[j].font_rid != font[1], "Incorrect font selected.");
+ }
+ if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) {
+ TEST_FAIL_COND(glyphs[j].font_rid != font[0], "Incorrect font selected.");
+ }
+ if (glyphs[j].start > 16) {
+ TEST_FAIL_COND(glyphs[j].font_rid != RID(), "Incorrect font selected.");
+ TEST_FAIL_COND(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index.");
+ }
+ TEST_FAIL_COND((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range.");
+ TEST_FAIL_COND(glyphs[j].font_size != 16, "Incorrect glyph font size.");
+ }
+
+ ts->free(ctx);
+
+ for (int j = 0; j < font.size(); j++) {
+ ts->free(font[j]);
+ }
+ font.clear();
+ }
+ }
+
+ SUBCASE("[TextServer] Text layout: BiDi") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ if (!ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {
+ continue;
+ }
+
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
+
+ Vector<RID> font;
+ font.push_back(font1);
+ font.push_back(font2);
+
+ String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)";
+ // 7^ 26^
+
+ RID ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+ int gl_size = ts->shaped_text_get_glyph_count(ctx);
+ TEST_FAIL_COND(gl_size == 0, "Shaping failed");
+ for (int j = 0; j < gl_size; j++) {
+ if (glyphs[j].count > 0) {
+ if (glyphs[j].start < 7) {
+ TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+ }
+ if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) {
+ TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+ }
+ if (glyphs[j].start > 26) {
+ TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+ }
+ }
+ }
+
+ ts->free(ctx);
+
+ for (int j = 0; j < font.size(); j++) {
+ ts->free(font[j]);
+ }
+ font.clear();
+ }
+ }
+
+ SUBCASE("[TextServer] Text layout: Line breaking") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ String test_1 = U"test test test";
+ // 5^ 10^
+
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
+
+ Vector<RID> font;
+ font.push_back(font1);
+ font.push_back(font2);
+
+ RID ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ bool ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ PackedInt32Array brks = ts->shaped_text_get_line_breaks(ctx, 1);
+ TEST_FAIL_COND(brks.size() != 6, "Invalid line breaks number.");
+ if (brks.size() == 6) {
+ TEST_FAIL_COND(brks[0] != 0, "Invalid line break position.");
+ TEST_FAIL_COND(brks[1] != 5, "Invalid line break position.");
+
+ TEST_FAIL_COND(brks[2] != 5, "Invalid line break position.");
+ TEST_FAIL_COND(brks[3] != 10, "Invalid line break position.");
+
+ TEST_FAIL_COND(brks[4] != 10, "Invalid line break position.");
+ TEST_FAIL_COND(brks[5] != 14, "Invalid line break position.");
+ }
+
+ ts->free(ctx);
+
+ for (int j = 0; j < font.size(); j++) {
+ ts->free(font[j]);
+ }
+ font.clear();
+ }
+ }
+
+ SUBCASE("[TextServer] Text layout: Justification") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
+
+ Vector<RID> font;
+ font.push_back(font1);
+ font.push_back(font2);
+
+ String test_1 = U"الحمد";
+ String test_2 = U"الحمد test";
+ String test_3 = U"test test";
+ // 7^ 26^
+
+ RID ctx;
+ bool ok;
+ float width_old, width;
+ if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
+ ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ width_old = ts->shaped_text_get_width(ctx);
+ width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+ TEST_FAIL_COND((width != width_old), "Invalid fill width.");
+ width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
+ TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+ ts->free(ctx);
+
+ ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ ok = ts->shaped_text_add_string(ctx, test_2, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ width_old = ts->shaped_text_get_width(ctx);
+ width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+ TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+ width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
+ TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+ ts->free(ctx);
+ }
+
+ ctx = ts->create_shaped_text();
+ TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+ ok = ts->shaped_text_add_string(ctx, test_3, font, 16);
+ TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+ width_old = ts->shaped_text_get_width(ctx);
+ width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+ TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+ ts->free(ctx);
+
+ for (int j = 0; j < font.size(); j++) {
+ ts->free(font[j]);
+ }
+ font.clear();
+ }
+ }
+
+ SUBCASE("[TextServer] Strip Diacritics") {
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+ if (ts->has_feature(TextServer::FEATURE_SHAPING)) {
+ CHECK(ts->strip_diacritics(U"ٱلسَّلَامُ عَلَيْكُمْ") == U"ٱلسلام عليكم");
+ }
+
+ CHECK(ts->strip_diacritics(U"pêches épinards tomates fraises") == U"peches epinards tomates fraises");
+ CHECK(ts->strip_diacritics(U"ΆΈΉΊΌΎΏΪΫϓϔ") == U"ΑΕΗΙΟΥΩΙΥΥΥ");
+ CHECK(ts->strip_diacritics(U"άέήίΐϊΰϋόύώ") == U"αεηιιιυυουω");
+ CHECK(ts->strip_diacritics(U"ЀЁЃ ЇЌЍӢӤЙ ЎӮӰӲ ӐӒӖӚӜӞ ӦӪ Ӭ Ӵ Ӹ") == U"ЕЕГ ІКИИИИ УУУУ ААЕӘЖЗ ОӨ Э Ч Ы");
+ CHECK(ts->strip_diacritics(U"ѐёѓ їќѝӣӥй ўӯӱӳ ӑӓӗӛӝӟ ӧӫ ӭ ӵ ӹ") == U"еег ікииии уууу ааеәжз оө э ч ы");
+ CHECK(ts->strip_diacritics(U"ÀÁÂÃÄÅĀĂĄÇĆĈĊČĎÈÉÊËĒĔĖĘĚĜĞĠĢĤÌÍÎÏĨĪĬĮİĴĶĹĻĽÑŃŅŇŊÒÓÔÕÖØŌŎŐƠŔŖŘŚŜŞŠŢŤÙÚÛÜŨŪŬŮŰŲƯŴÝŶŹŻŽ") == U"AAAAAAAAACCCCCDEEEEEEEEEGGGGHIIIIIIIIIJKLLLNNNNŊOOOOOØOOOORRRSSSSTTUUUUUUUUUUUWYYZZZ");
+ CHECK(ts->strip_diacritics(U"àáâãäåāăąçćĉċčďèéêëēĕėęěĝğġģĥìíîïĩīĭįĵķĺļľñńņňŋòóôõöøōŏőơŕŗřśŝşšţťùúûüũūŭůűųưŵýÿŷźżž") == U"aaaaaaaaacccccdeeeeeeeeegggghiiiiiiiijklllnnnnŋoooooøoooorrrssssttuuuuuuuuuuuwyyyzzz");
+ CHECK(ts->strip_diacritics(U"ǍǏȈǑǪǬȌȎȪȬȮȰǓǕǗǙǛȔȖǞǠǺȀȂȦǢǼǦǴǨǸȆȐȒȘȚȞȨ Ḁ ḂḄḆ Ḉ ḊḌḎḐḒ ḔḖḘḚḜ Ḟ Ḡ ḢḤḦḨḪ ḬḮ ḰḲḴ ḶḸḺḼ ḾṀṂ ṄṆṈṊ ṌṎṐṒ ṔṖ ṘṚṜṞ ṠṢṤṦṨ ṪṬṮṰ ṲṴṶṸṺ") == U"AIIOOOOOOOOOUUUUUUUAAAAAAÆÆGGKNERRSTHE A BBB C DDDDD EEEEE F G HHHHH II KKK LLLL MMM NNNN OOOO PP RRRR SSSSS TTTT UUUUU");
+ CHECK(ts->strip_diacritics(U"ǎǐȉȋǒǫǭȍȏȫȭȯȱǔǖǘǚǜȕȗǟǡǻȁȃȧǣǽǧǵǩǹȇȑȓșțȟȩ ḁ ḃḅḇ ḉ ḋḍḏḑḓ ḟ ḡ ḭḯ ḱḳḵ ḷḹḻḽ ḿṁṃ ṅṇṉṋ ṍṏṑṓ ṗṕ ṙṛṝṟ ṡṣṥṧṩ ṫṭṯṱ ṳṵṷṹṻ") == U"aiiiooooooooouuuuuuuaaaaaaææggknerrsthe a bbb c ddddd f g ii kkk llll mmm nnnn oooo pp rrrr sssss tttt uuuuu");
+ CHECK(ts->strip_diacritics(U"ṼṾ ẀẂẄẆẈ ẊẌ Ẏ ẐẒẔ") == U"VV WWWWW XX Y ZZZ");
+ CHECK(ts->strip_diacritics(U"ṽṿ ẁẃẅẇẉ ẋẍ ẏ ẑẓẕ ẖ ẗẘẙẛ") == U"vv wwwww xx y zzz h twys");
+ }
+ }
+ }
+}
+}; // namespace TestTextServer
+
+#endif // TEST_TEXT_SERVER_H
+#endif // TOOLS_ENABLED