- Add CMakeLists.txt for building TypePHP Ocean Godot bridge extension - Create main.php game logic with chunk-based world generation and weather system - Add Godot scene configuration with environment settings and particle effects - Implement PHP stubs for ocean backend functions - Add complete C++ backend implementation with OpenGL rendering and physics - Integrate spectral wave simulation and dynamic weather transitions - Implement chunk loading/unloading system for infinite world navigation - Add mouse capture and input handling for 3D camera controls - Create marker placement system for world objects and islandspull/16/head
parent
348c3d01fc
commit
e5dbf10ceb
21 changed files with 3211 additions and 0 deletions
|
After Width: | Height: | Size: 831 KiB |
@ -0,0 +1,98 @@ |
||||
# TypePHP Ocean Demo |
||||
|
||||
这是一个新的 OpenGL 示例项目,场景为海洋、天空和一条可移动小船。它复用 `minecraft-demo` 的 Win32/WGL 思路,但不使用方块世界,重点展示程序化水面和天气变化。 |
||||
|
||||
## 特性 |
||||
|
||||
- PHP/TypePHP 负责游戏逻辑:小船移动、惯性、昼夜循环、随机天气切换。 |
||||
- C++ 只负责底层窗口、键盘输入、OpenGL 渲染和退出确认框。 |
||||
- 程序化天空:白天、夜晚、傍晚、清晨的颜色变化。 |
||||
- 程序化 skybox:远景只保留镜头旋转,不参与世界平移。 |
||||
- 程序化海面:动态波浪、法线扰动、漫反射、镜面高光和菲涅尔近似。 |
||||
- 参考 `D:\workspace\Ocean-Simulation\Ocean_Simulation` 的 Phillips Spectrum、PBR/Fresnel、roughness/AO 和指数雾思路,移植为固定管线可运行的轻量版本。 |
||||
- 天气:晴天、阴天、雨天随机变化。 |
||||
- 小船支持 `W/A/S/D` 四方向移动。 |
||||
- 鼠标控制自由摄像机视角。 |
||||
- WASD 采用类似 Minecraft 的镜头相对移动:`W` 向镜头前方,`A/D` 横向移动。 |
||||
- 开放世界分块:PHP 按小船所在 chunk 渐进生成和卸载海上对象。 |
||||
- 海上对象包括浮标、灯浮标和小礁石,用于提供移动参照和远景层次。 |
||||
- 新增程序化海岛和大型帆船对象,尝试接近 `1r.png` 的前景船、中景岛、远景层次构图。 |
||||
- 远处线性雾效,使海平线自然融合。 |
||||
- HDR 风格 tone mapping,使强高光和暗部层次更自然。 |
||||
- Bloom 近似:太阳、晨昏地平线和夜间船灯有柔和光晕。 |
||||
- 夜间小船灯作为点光源影响近处水面。 |
||||
- 参考 `D:\workspace\volumetric-clouds` 的体积云 shader,将 coverage、wind shear、多层噪声、Beer-Lambert 透射、powder effect 和 Henyey-Greenstein 相位函数移植为固定管线可绘制的分层云近似。 |
||||
|
||||
## 关于 ref1.txt 建议 |
||||
|
||||
`ref1.txt` 建议使用 OpenGL 3.3、GLFW、GLEW/GLAD、GLM 和 stb_image。这个示例为了保持 TypePHP 示例项目轻量、可直接用当前编译器构建,继续使用 Win32/WGL 和 OpenGL 固定管线,没有引入额外第三方依赖。 |
||||
|
||||
已按同类思路实现: |
||||
|
||||
- 天空盒式远景层 |
||||
- 动态水面网格 |
||||
- 多正弦波浪 |
||||
- 基础光照、高光和菲涅尔近似 |
||||
- 鼠标摄像机控制 |
||||
- 雾效 |
||||
|
||||
## 关于 Ocean-Simulation |
||||
|
||||
`D:\workspace\Ocean-Simulation\Ocean_Simulation` 使用现代 OpenGL shader、tessellation、OpenCL FFT、高度贴图、HDR skybox 和 PBR/IBL。当前 TypePHP demo 没有引入这些运行时依赖,而是提取了其中适合示例项目的核心算法思想: |
||||
|
||||
- 多频谱波浪叠加,使用深水色散关系 `sqrt(g * k)`。 |
||||
- 斜率推导法线、roughness 和 ambient occlusion。 |
||||
- Fresnel-Schlick 近似。 |
||||
- GGX/Reitz 风格高光分布的轻量化版本。 |
||||
- 指数平方雾效,使海面远处自然融入天空。 |
||||
|
||||
## 关于 ref2.txt 建议 |
||||
|
||||
`ref2.txt` 进一步建议 PBR/IBL、FFT、HDR、Bloom、多光源和大气雾效。当前示例仍保持低依赖固定管线实现,但继续补上了可落地的近似: |
||||
|
||||
- HDR 风格曝光和 tone mapping。 |
||||
- 屏幕空间 Bloom 近似,不依赖 framebuffer 后处理。 |
||||
- 夜间船灯点光源。 |
||||
- 水面材质继续使用 Fresnel、roughness、AO 和高光分布。 |
||||
- 继续保留指数平方雾效来模拟大气融合。 |
||||
|
||||
## 关于 volumetric-clouds |
||||
|
||||
`D:\workspace\volumetric-clouds` 使用 OpenGL 4、3D 噪声纹理、compute shader 和全屏 ray marching。当前示例没有引入这些依赖,而是提取核心思路做轻量实现: |
||||
|
||||
- 使用 value noise + Worley FBM 近似云密度。 |
||||
- 使用 coverage 控制云层覆盖率,天气越差云层越厚。 |
||||
- 使用 wind shear 和时间偏移让云层缓慢移动。 |
||||
- 使用 Beer-Lambert 透射模拟云体厚度。 |
||||
- 使用 powder effect 和 Henyey-Greenstein 相位函数近似云层散射。 |
||||
- 使用多层半透明 quad strip 叠加,替代原先简单椭圆云。 |
||||
- 海洋颜色使用深海吸收色、浅层散射、天空反射和雨天灰蓝衰减,比早期的亮青色更接近真实海面。 |
||||
|
||||
## 已知视觉问题修正 |
||||
|
||||
- 早期 skybox 使用立方体四个侧面,镜头旋转时会看到类似四面“屏障”的硬边界。现在已改为半球天空穹顶。 |
||||
- 早期水面是方形网格并按单方向淡出,远处边缘可能像边界。现在改为更大范围的水面网格,并按径向距离平滑淡出。 |
||||
|
||||
## 编译 |
||||
|
||||
```powershell |
||||
php bin\compiler.php examples\ocean-demo\project.yml |
||||
``` |
||||
|
||||
编译产物输出到仓库根目录: |
||||
|
||||
```text |
||||
ocean_demo.exe |
||||
``` |
||||
|
||||
## 运行 |
||||
|
||||
```powershell |
||||
.\ocean_demo.exe |
||||
``` |
||||
|
||||
## 操作 |
||||
|
||||
- `W/A/S/D` 移动小船 |
||||
- 鼠标移动视角 |
||||
- `Esc` 弹出退出确认框 |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,287 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
const KEY_W = 0x57; |
||||
const KEY_A = 0x41; |
||||
const KEY_S = 0x53; |
||||
const KEY_D = 0x44; |
||||
const KEY_ESCAPE = 0x1B; |
||||
|
||||
const WEATHER_SUNNY = 0; |
||||
const WEATHER_CLOUDY = 1; |
||||
const WEATHER_RAIN = 2; |
||||
const WORLD_CHUNK_SIZE = 96; |
||||
const RENDER_CHUNK_RADIUS = 4; |
||||
const KEEP_CHUNK_RADIUS = 5; |
||||
const CHUNKS_PER_FRAME = 1; |
||||
|
||||
function weather_name(int $weather): string |
||||
{ |
||||
if ($weather === WEATHER_CLOUDY) { |
||||
return 'cloudy'; |
||||
} |
||||
if ($weather === WEATHER_RAIN) { |
||||
return 'rainy'; |
||||
} |
||||
return 'sunny'; |
||||
} |
||||
|
||||
function choose_next_weather(int $current): int |
||||
{ |
||||
$roll = mt_rand(0, 99); |
||||
if ($current === WEATHER_SUNNY) { |
||||
return $roll < 58 ? WEATHER_CLOUDY : ($roll < 78 ? WEATHER_RAIN : WEATHER_SUNNY); |
||||
} |
||||
if ($current === WEATHER_CLOUDY) { |
||||
return $roll < 42 ? WEATHER_SUNNY : ($roll < 76 ? WEATHER_RAIN : WEATHER_CLOUDY); |
||||
} |
||||
return $roll < 52 ? WEATHER_CLOUDY : ($roll < 76 ? WEATHER_SUNNY : WEATHER_RAIN); |
||||
} |
||||
|
||||
function floor_chunk(float $value): int |
||||
{ |
||||
$v = (int) floor($value); |
||||
if ($v >= 0) { |
||||
return intdiv($v, WORLD_CHUNK_SIZE); |
||||
} |
||||
return -intdiv(-$v + WORLD_CHUNK_SIZE - 1, WORLD_CHUNK_SIZE); |
||||
} |
||||
|
||||
function chunk_key(int $chunkX, int $chunkZ): string |
||||
{ |
||||
return (string) $chunkX . ':' . (string) $chunkZ; |
||||
} |
||||
|
||||
function hash01(int $x, int $z, int $salt): float |
||||
{ |
||||
$n = sin($x * 127.1 + $z * 311.7 + $salt * 74.7) * 43758.5453123; |
||||
return $n - floor($n); |
||||
} |
||||
|
||||
function sort_pending_chunks(array $pendingChunks, int $centerChunkX, int $centerChunkZ): array |
||||
{ |
||||
uasort($pendingChunks, static function (array $a, array $b) use ($centerChunkX, $centerChunkZ): int { |
||||
$adx = (int) $a[0] - $centerChunkX; |
||||
$adz = (int) $a[1] - $centerChunkZ; |
||||
$bdx = (int) $b[0] - $centerChunkX; |
||||
$bdz = (int) $b[1] - $centerChunkZ; |
||||
return ($adx * $adx + $adz * $adz) <=> ($bdx * $bdx + $bdz * $bdz); |
||||
}); |
||||
return $pendingChunks; |
||||
} |
||||
|
||||
function enqueue_visible_chunks(int $centerChunkX, int $centerChunkZ, array $loadedChunks, array $pendingChunks): array |
||||
{ |
||||
$queued = 0; |
||||
for ($cx = $centerChunkX - RENDER_CHUNK_RADIUS; $cx <= $centerChunkX + RENDER_CHUNK_RADIUS; $cx++) { |
||||
for ($cz = $centerChunkZ - RENDER_CHUNK_RADIUS; $cz <= $centerChunkZ + RENDER_CHUNK_RADIUS; $cz++) { |
||||
$key = chunk_key($cx, $cz); |
||||
if (!isset($loadedChunks[$key]) && !isset($pendingChunks[$key])) { |
||||
$pendingChunks[$key] = [$cx, $cz]; |
||||
$queued++; |
||||
} |
||||
} |
||||
} |
||||
return $queued > 0 ? sort_pending_chunks($pendingChunks, $centerChunkX, $centerChunkZ) : $pendingChunks; |
||||
} |
||||
|
||||
function generate_world_chunk(int $chunkX, int $chunkZ): void |
||||
{ |
||||
ocean_begin_chunk($chunkX, $chunkZ); |
||||
$baseX = $chunkX * WORLD_CHUNK_SIZE; |
||||
$baseZ = $chunkZ * WORLD_CHUNK_SIZE; |
||||
$chunkRoll = hash01($chunkX, $chunkZ, 101); |
||||
if (($chunkX === 0 && $chunkZ === -2) || $chunkRoll > 0.86) { |
||||
$x = $baseX + WORLD_CHUNK_SIZE * (0.35 + hash01($chunkX, $chunkZ, 102) * 0.30); |
||||
$z = $baseZ + WORLD_CHUNK_SIZE * (0.30 + hash01($chunkX, $chunkZ, 103) * 0.38); |
||||
ocean_add_marker($x, $z, 3, 8.0 + hash01($chunkX, $chunkZ, 104) * 5.0); |
||||
} |
||||
if (($chunkX === -1 && $chunkZ === -1) || $chunkRoll < 0.08) { |
||||
$x = $baseX + WORLD_CHUNK_SIZE * (0.42 + hash01($chunkX, $chunkZ, 105) * 0.24); |
||||
$z = $baseZ + WORLD_CHUNK_SIZE * (0.36 + hash01($chunkX, $chunkZ, 106) * 0.30); |
||||
ocean_add_marker($x, $z, 4, 5.5 + hash01($chunkX, $chunkZ, 107) * 2.4); |
||||
} |
||||
for ($i = 0; $i < 5; $i++) { |
||||
$chance = hash01($chunkX * 17 + $i, $chunkZ * 19 - $i, 3); |
||||
if ($chance < 0.48) { |
||||
continue; |
||||
} |
||||
$x = $baseX + hash01($chunkX, $chunkZ, 20 + $i) * WORLD_CHUNK_SIZE; |
||||
$z = $baseZ + hash01($chunkX, $chunkZ, 40 + $i) * WORLD_CHUNK_SIZE; |
||||
$typeRoll = hash01($chunkX, $chunkZ, 60 + $i); |
||||
$type = $typeRoll > 0.78 ? 2 : ($typeRoll > 0.52 ? 1 : 0); |
||||
$size = 0.8 + hash01($chunkX, $chunkZ, 80 + $i) * 1.8; |
||||
ocean_add_marker($x, $z, $type, $size); |
||||
} |
||||
ocean_commit_chunk($chunkX, $chunkZ); |
||||
} |
||||
|
||||
function process_chunk_queue(array $loadedChunks, array $pendingChunks, int $maxChunks): array |
||||
{ |
||||
$loaded = 0; |
||||
foreach ($pendingChunks as $key => $chunk) { |
||||
generate_world_chunk((int) $chunk[0], (int) $chunk[1]); |
||||
$loadedChunks[$key] = [(int) $chunk[0], (int) $chunk[1]]; |
||||
unset($pendingChunks[$key]); |
||||
$loaded++; |
||||
if ($loaded >= $maxChunks) { |
||||
break; |
||||
} |
||||
} |
||||
return [$loadedChunks, $pendingChunks]; |
||||
} |
||||
|
||||
function unload_far_chunks(int $centerChunkX, int $centerChunkZ, array $loadedChunks): array |
||||
{ |
||||
foreach ($loadedChunks as $key => $chunk) { |
||||
$dx = abs((int) $chunk[0] - $centerChunkX); |
||||
$dz = abs((int) $chunk[1] - $centerChunkZ); |
||||
if ($dx > KEEP_CHUNK_RADIUS || $dz > KEEP_CHUNK_RADIUS) { |
||||
ocean_remove_chunk((int) $chunk[0], (int) $chunk[1]); |
||||
unset($loadedChunks[$key]); |
||||
} |
||||
} |
||||
return $loadedChunks; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
if (!ocean_init('TypePHP Ocean Demo - OpenGL', 1280, 720)) { |
||||
echo "OpenGL init failed\n"; |
||||
return; |
||||
} |
||||
|
||||
mt_srand((int) (ocean_get_time() * 1000000.0)); |
||||
|
||||
$x = 0.0; |
||||
$z = 0.0; |
||||
$yaw = 0.0; |
||||
$velocityX = 0.0; |
||||
$velocityZ = 0.0; |
||||
$cameraYaw = 0.0; |
||||
$cameraPitch = 0.24; |
||||
$cameraDistance = 24.0; |
||||
$last = ocean_get_time(); |
||||
$escapeWasDown = false; |
||||
|
||||
$dayTime = 0.28; |
||||
$weather = WEATHER_SUNNY; |
||||
$targetWeather = WEATHER_CLOUDY; |
||||
$weatherMix = 0.0; |
||||
$nextWeatherAt = $last + 18.0; |
||||
$centerChunkX = floor_chunk($x); |
||||
$centerChunkZ = floor_chunk($z); |
||||
$loadedChunks = []; |
||||
$pendingChunks = enqueue_visible_chunks($centerChunkX, $centerChunkZ, [], []); |
||||
while (count($pendingChunks) > 0) { |
||||
$queueState = process_chunk_queue($loadedChunks, $pendingChunks, 4); |
||||
$loadedChunks = $queueState[0]; |
||||
$pendingChunks = $queueState[1]; |
||||
ocean_poll_events(); |
||||
} |
||||
|
||||
while (!ocean_should_close()) { |
||||
$now = ocean_get_time(); |
||||
$dt = max(0.001, min(0.05, $now - $last)); |
||||
$last = $now; |
||||
|
||||
ocean_poll_events(); |
||||
$escapeDown = ocean_key_pressed(KEY_ESCAPE); |
||||
if ($escapeDown && !$escapeWasDown) { |
||||
if (ocean_confirm_exit()) { |
||||
break; |
||||
} |
||||
$last = ocean_get_time(); |
||||
} |
||||
$escapeWasDown = $escapeDown; |
||||
|
||||
$cameraYaw -= ocean_mouse_delta_x() * 0.0025; |
||||
$cameraPitch += ocean_mouse_delta_y() * 0.0020; |
||||
$cameraPitch = max(-0.20, min(0.85, $cameraPitch)); |
||||
|
||||
$inputX = 0.0; |
||||
$inputZ = 0.0; |
||||
if (ocean_key_pressed(KEY_W)) { |
||||
$inputZ -= 1.0; |
||||
} |
||||
if (ocean_key_pressed(KEY_S)) { |
||||
$inputZ += 1.0; |
||||
} |
||||
if (ocean_key_pressed(KEY_A)) { |
||||
$inputX -= 1.0; |
||||
} |
||||
if (ocean_key_pressed(KEY_D)) { |
||||
$inputX += 1.0; |
||||
} |
||||
|
||||
$len = sqrt($inputX * $inputX + $inputZ * $inputZ); |
||||
if ($len > 0.0) { |
||||
$inputX /= $len; |
||||
$inputZ /= $len; |
||||
} |
||||
|
||||
$accel = 18.0; |
||||
$drag = pow(0.08, $dt); |
||||
$forwardX = -sin($cameraYaw); |
||||
$forwardZ = -cos($cameraYaw); |
||||
$rightX = cos($cameraYaw); |
||||
$rightZ = -sin($cameraYaw); |
||||
$worldInputX = $rightX * $inputX + $forwardX * -$inputZ; |
||||
$worldInputZ = $rightZ * $inputX + $forwardZ * -$inputZ; |
||||
if ($len > 0.0) { |
||||
$yaw = atan2($worldInputX, -$worldInputZ); |
||||
} |
||||
$velocityX = ($velocityX + $worldInputX * $accel * $dt) * $drag; |
||||
$velocityZ = ($velocityZ + $worldInputZ * $accel * $dt) * $drag; |
||||
$x += $velocityX * $dt; |
||||
$z += $velocityZ * $dt; |
||||
$speed = sqrt($velocityX * $velocityX + $velocityZ * $velocityZ); |
||||
|
||||
$currentChunkX = floor_chunk($x); |
||||
$currentChunkZ = floor_chunk($z); |
||||
if ($currentChunkX !== $centerChunkX || $currentChunkZ !== $centerChunkZ) { |
||||
$centerChunkX = $currentChunkX; |
||||
$centerChunkZ = $currentChunkZ; |
||||
$pendingChunks = enqueue_visible_chunks($centerChunkX, $centerChunkZ, $loadedChunks, $pendingChunks); |
||||
$loadedChunks = unload_far_chunks($centerChunkX, $centerChunkZ, $loadedChunks); |
||||
} |
||||
$queueState = process_chunk_queue($loadedChunks, $pendingChunks, CHUNKS_PER_FRAME); |
||||
$loadedChunks = $queueState[0]; |
||||
$pendingChunks = $queueState[1]; |
||||
|
||||
$dayTime += $dt / 96.0; |
||||
if ($dayTime >= 1.0) { |
||||
$dayTime -= 1.0; |
||||
} |
||||
|
||||
if ($now >= $nextWeatherAt && $targetWeather === $weather) { |
||||
$targetWeather = choose_next_weather($weather); |
||||
$weatherMix = 0.0; |
||||
$nextWeatherAt = $now + 16.0 + mt_rand(0, 16); |
||||
echo 'Weather changed: ' . weather_name($targetWeather) . PHP_EOL; |
||||
} |
||||
if ($targetWeather !== $weather) { |
||||
$weatherMix = min(1.0, $weatherMix + $dt / 7.0); |
||||
if ($weatherMix >= 1.0) { |
||||
$weather = $targetWeather; |
||||
$weatherMix = 0.0; |
||||
} |
||||
} |
||||
|
||||
$rainAmount = $weather === WEATHER_RAIN ? 1.0 : 0.0; |
||||
if ($targetWeather === WEATHER_RAIN) { |
||||
$rainAmount = max($rainAmount, $weatherMix); |
||||
} elseif ($weather === WEATHER_RAIN) { |
||||
$rainAmount = 1.0 - $weatherMix; |
||||
} |
||||
|
||||
ocean_set_boat($x, $z, $yaw, $speed); |
||||
ocean_set_camera($cameraYaw, $cameraPitch, $cameraDistance); |
||||
ocean_set_environment($dayTime, $targetWeather, $weatherMix, max(0.0, min(1.0, $rainAmount))); |
||||
ocean_render_frame(); |
||||
ocean_sleep(1); |
||||
} |
||||
|
||||
ocean_shutdown(); |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
<?php |
||||
|
||||
function ocean_init(string $title, int $width, int $height): bool {} |
||||
function ocean_shutdown(): void {} |
||||
function ocean_should_close(): bool {} |
||||
function ocean_poll_events(): void {} |
||||
function ocean_key_pressed(int $key): bool {} |
||||
function ocean_mouse_delta_x(): float {} |
||||
function ocean_mouse_delta_y(): float {} |
||||
function ocean_get_time(): float {} |
||||
function ocean_sleep(int $milliseconds): void {} |
||||
function ocean_confirm_exit(): bool {} |
||||
function ocean_set_boat(float $x, float $z, float $yaw, float $speed): void {} |
||||
function ocean_set_camera(float $yaw, float $pitch, float $distance): void {} |
||||
function ocean_set_environment(float $dayTime, int $weather, float $weatherMix, float $rainAmount): void {} |
||||
function ocean_begin_chunk(int $chunkX, int $chunkZ): void {} |
||||
function ocean_add_marker(float $x, float $z, int $type, float $size): void {} |
||||
function ocean_commit_chunk(int $chunkX, int $chunkZ): void {} |
||||
function ocean_remove_chunk(int $chunkX, int $chunkZ): void {} |
||||
function ocean_render_frame(): void {} |
||||
@ -0,0 +1,12 @@ |
||||
name: ocean_demo |
||||
version: 0.1.0 |
||||
mode: bin |
||||
cxx-std: c++17 |
||||
sources: |
||||
- main.php |
||||
- php-src |
||||
- cpp-src |
||||
cxx-flags: |
||||
- "/D_CRT_SECURE_NO_WARNINGS" |
||||
ld-flags: |
||||
- "opengl32.lib" |
||||
|
After Width: | Height: | Size: 95 KiB |
@ -0,0 +1,38 @@ |
||||
# TypePHP Ocean Odyssey |
||||
|
||||
Godot + TypePHP open ocean demo with procedural ocean, sky, clouds, rain, islands, navigation markers, and a controllable sailboat. |
||||
|
||||
Godot owns rendering, input, camera, weather presentation, and procedural meshes. TypePHP owns deterministic world data and weather transitions once the bridge DLL is built. The Godot scene still runs with fallback data if the native bridge is not present. |
||||
|
||||
## Run in Godot |
||||
|
||||
```powershell |
||||
D:\workspace\godot\Godot_v4.7-stable_win64_console.exe --path examples\ocean-godot |
||||
``` |
||||
|
||||
Controls: |
||||
|
||||
- `W/A/S/D` sail relative to the camera |
||||
- `Shift` boost |
||||
- Mouse to orbit the camera |
||||
- `Esc` release or capture the mouse |
||||
|
||||
## Build TypePHP library |
||||
|
||||
```powershell |
||||
php bin\compiler.php examples\ocean-godot\typephp.yml -f |
||||
Copy-Item -LiteralPath typephp_ocean.dll -Destination examples\ocean-godot\bin\typephp_ocean.dll -Force |
||||
``` |
||||
|
||||
## Build Godot GDExtension |
||||
|
||||
```powershell |
||||
cmake -S examples\ocean-godot\gdextension-src -B examples\ocean-godot\gdextension-src\build-nmake -G "NMake Makefiles" -DCMAKE_TOOLCHAIN_FILE=D:\workspace\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DCMAKE_BUILD_TYPE=Release |
||||
cmake --build examples\ocean-godot\gdextension-src\build-nmake --config Release |
||||
``` |
||||
|
||||
The bridge outputs: |
||||
|
||||
```text |
||||
examples\ocean-godot\bin\typephp_ocean_godot_bridge.dll |
||||
``` |
||||
@ -0,0 +1,102 @@ |
||||
#include <php_typephp_ocean_func_decl.h> |
||||
|
||||
#ifdef _WIN32 |
||||
#define TYPEPHP_OCEAN_API extern "C" __declspec(dllexport) |
||||
#else |
||||
#define TYPEPHP_OCEAN_API extern "C" __attribute__((visibility("default"))) |
||||
#endif |
||||
|
||||
extern "C" int php_aot_runtime_init(int argc, char **argv); |
||||
extern "C" void php_aot_runtime_shutdown(); |
||||
|
||||
static bool g_typephp_ocean_initialized = false; |
||||
|
||||
static int typephp_ocean_ensure_runtime() |
||||
{ |
||||
if (g_typephp_ocean_initialized) { |
||||
return 1; |
||||
} |
||||
|
||||
char app_name[] = "typephp_ocean"; |
||||
char *argv[] = {app_name, nullptr}; |
||||
if (php_aot_runtime_init(1, argv) != 0) { |
||||
return 0; |
||||
} |
||||
|
||||
g_typephp_ocean_initialized = true; |
||||
return 1; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API int typephp_ocean_init() |
||||
{ |
||||
return typephp_ocean_ensure_runtime(); |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API void typephp_ocean_shutdown() |
||||
{ |
||||
if (!g_typephp_ocean_initialized) { |
||||
return; |
||||
} |
||||
php_aot_runtime_shutdown(); |
||||
g_typephp_ocean_initialized = false; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API int typephp_ocean_island_count() |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<int>(php_ocean_island_count()) : 0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_island_x(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_island_x(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_island_z(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_island_z(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_island_radius(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_island_radius(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_island_height(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_island_height(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_island_seed(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_island_seed(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API int typephp_ocean_marker_count() |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<int>(php_ocean_marker_count()) : 0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_marker_x(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_marker_x(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_marker_z(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_marker_z(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API int typephp_ocean_marker_type(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<int>(php_ocean_marker_type(index)) : 0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API double typephp_ocean_marker_size(int index) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<double>(php_ocean_marker_size(index)) : 0.0; |
||||
} |
||||
|
||||
TYPEPHP_OCEAN_API int typephp_ocean_choose_next_weather(int current, double roll) |
||||
{ |
||||
return typephp_ocean_ensure_runtime() ? static_cast<int>(php_ocean_choose_next_weather(current, roll)) : current; |
||||
} |
||||
@ -0,0 +1,24 @@ |
||||
cmake_minimum_required(VERSION 3.21) |
||||
project(typephp_ocean_godot_bridge LANGUAGES CXX) |
||||
|
||||
set(CMAKE_CXX_STANDARD 17) |
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) |
||||
|
||||
find_package(unofficial-godot-cpp CONFIG REQUIRED) |
||||
|
||||
add_library(typephp_ocean_godot_bridge SHARED |
||||
typephp_ocean_bridge.cpp |
||||
register_types.cpp |
||||
) |
||||
|
||||
target_link_libraries(typephp_ocean_godot_bridge PRIVATE unofficial::godot::cpp) |
||||
|
||||
if (MSVC) |
||||
target_compile_options(typephp_ocean_godot_bridge PRIVATE /EHsc /bigobj) |
||||
endif() |
||||
|
||||
set_target_properties(typephp_ocean_godot_bridge PROPERTIES |
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin" |
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin" |
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin" |
||||
) |
||||
@ -0,0 +1,33 @@ |
||||
#include "typephp_ocean_bridge.hpp" |
||||
|
||||
#include <godot_cpp/core/class_db.hpp> |
||||
#include <godot_cpp/godot.hpp> |
||||
|
||||
using namespace godot; |
||||
|
||||
void initialize_typephp_ocean_bridge(ModuleInitializationLevel level) |
||||
{ |
||||
if (level != MODULE_INITIALIZATION_LEVEL_SCENE) { |
||||
return; |
||||
} |
||||
GDREGISTER_CLASS(TypePhpOceanBridge); |
||||
} |
||||
|
||||
void uninitialize_typephp_ocean_bridge(ModuleInitializationLevel level) |
||||
{ |
||||
if (level != MODULE_INITIALIZATION_LEVEL_SCENE) { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
extern "C" GDExtensionBool GDE_EXPORT typephp_ocean_bridge_init( |
||||
GDExtensionInterfaceGetProcAddress get_proc_address, |
||||
GDExtensionClassLibraryPtr library, |
||||
GDExtensionInitialization *initialization) |
||||
{ |
||||
GDExtensionBinding::InitObject init_obj(get_proc_address, library, initialization); |
||||
init_obj.register_initializer(initialize_typephp_ocean_bridge); |
||||
init_obj.register_terminator(uninitialize_typephp_ocean_bridge); |
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); |
||||
return init_obj.init(); |
||||
} |
||||
@ -0,0 +1,141 @@ |
||||
#include "typephp_ocean_bridge.hpp" |
||||
|
||||
#include <godot_cpp/core/class_db.hpp> |
||||
#include <godot_cpp/variant/dictionary.hpp> |
||||
|
||||
#include <cmath> |
||||
|
||||
#ifdef _WIN32 |
||||
extern "C" IMAGE_DOS_HEADER __ImageBase; |
||||
#endif |
||||
|
||||
namespace godot { |
||||
|
||||
void TypePhpOceanBridge::_bind_methods() |
||||
{ |
||||
ClassDB::bind_method(D_METHOD("get_islands", "radius"), &TypePhpOceanBridge::get_islands); |
||||
ClassDB::bind_method(D_METHOD("get_markers", "radius"), &TypePhpOceanBridge::get_markers); |
||||
ClassDB::bind_method(D_METHOD("choose_next_weather", "current", "roll"), &TypePhpOceanBridge::choose_next_weather); |
||||
} |
||||
|
||||
TypePhpOceanBridge::TypePhpOceanBridge() = default; |
||||
|
||||
TypePhpOceanBridge::~TypePhpOceanBridge() |
||||
{ |
||||
#ifdef _WIN32 |
||||
if (library != nullptr) { |
||||
FreeLibrary(library); |
||||
library = nullptr; |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
bool TypePhpOceanBridge::ensure_loaded() |
||||
{ |
||||
#ifndef _WIN32 |
||||
return false; |
||||
#else |
||||
if (library != nullptr) { |
||||
return ocean_init != nullptr; |
||||
} |
||||
|
||||
wchar_t module_path[MAX_PATH]; |
||||
const DWORD len = GetModuleFileNameW(reinterpret_cast<HMODULE>(&__ImageBase), module_path, MAX_PATH); |
||||
if (len == 0 || len >= MAX_PATH) { |
||||
return false; |
||||
} |
||||
|
||||
std::wstring path(module_path, len); |
||||
const size_t slash = path.find_last_of(L"\\/"); |
||||
if (slash != std::wstring::npos) { |
||||
path.resize(slash + 1); |
||||
} else { |
||||
path.clear(); |
||||
} |
||||
path += L"typephp_ocean.dll"; |
||||
|
||||
library = LoadLibraryW(path.c_str()); |
||||
if (library == nullptr) { |
||||
return false; |
||||
} |
||||
|
||||
ocean_init = reinterpret_cast<FnInit>(GetProcAddress(library, "typephp_ocean_init")); |
||||
island_count = reinterpret_cast<FnCount>(GetProcAddress(library, "typephp_ocean_island_count")); |
||||
island_x = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_island_x")); |
||||
island_z = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_island_z")); |
||||
island_radius = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_island_radius")); |
||||
island_height = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_island_height")); |
||||
island_seed = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_island_seed")); |
||||
marker_count = reinterpret_cast<FnCount>(GetProcAddress(library, "typephp_ocean_marker_count")); |
||||
marker_x = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_marker_x")); |
||||
marker_z = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_marker_z")); |
||||
marker_type = reinterpret_cast<FnIntAt>(GetProcAddress(library, "typephp_ocean_marker_type")); |
||||
marker_size = reinterpret_cast<FnDoubleAt>(GetProcAddress(library, "typephp_ocean_marker_size")); |
||||
choose_weather = reinterpret_cast<FnWeather>(GetProcAddress(library, "typephp_ocean_choose_next_weather")); |
||||
|
||||
return ocean_init != nullptr && island_count != nullptr && island_x != nullptr && island_z != nullptr && |
||||
island_radius != nullptr && island_height != nullptr && island_seed != nullptr && |
||||
marker_count != nullptr && marker_x != nullptr && marker_z != nullptr && |
||||
marker_type != nullptr && marker_size != nullptr && choose_weather != nullptr && |
||||
ocean_init() != 0; |
||||
#endif |
||||
} |
||||
|
||||
Array TypePhpOceanBridge::get_islands(double radius) |
||||
{ |
||||
Array result; |
||||
if (!ensure_loaded()) { |
||||
return result; |
||||
} |
||||
|
||||
const int count = island_count(); |
||||
for (int i = 0; i < count; i++) { |
||||
const double x = island_x(i); |
||||
const double z = island_z(i); |
||||
if (std::sqrt(x * x + z * z) > radius) { |
||||
continue; |
||||
} |
||||
Dictionary island; |
||||
island["x"] = x; |
||||
island["z"] = z; |
||||
island["radius"] = island_radius(i); |
||||
island["height"] = island_height(i); |
||||
island["seed"] = island_seed(i); |
||||
result.append(island); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
Array TypePhpOceanBridge::get_markers(double radius) |
||||
{ |
||||
Array result; |
||||
if (!ensure_loaded()) { |
||||
return result; |
||||
} |
||||
|
||||
const int count = marker_count(); |
||||
for (int i = 0; i < count; i++) { |
||||
const double x = marker_x(i); |
||||
const double z = marker_z(i); |
||||
if (std::sqrt(x * x + z * z) > radius) { |
||||
continue; |
||||
} |
||||
Dictionary marker; |
||||
marker["x"] = x; |
||||
marker["z"] = z; |
||||
marker["type"] = marker_type(i); |
||||
marker["size"] = marker_size(i); |
||||
result.append(marker); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
int TypePhpOceanBridge::choose_next_weather(int current, double roll) |
||||
{ |
||||
if (!ensure_loaded()) { |
||||
return current; |
||||
} |
||||
return choose_weather(current, roll); |
||||
} |
||||
|
||||
} // namespace godot
|
||||
@ -0,0 +1,53 @@ |
||||
#pragma once |
||||
|
||||
#include <godot_cpp/classes/node.hpp> |
||||
#include <godot_cpp/variant/array.hpp> |
||||
|
||||
#ifdef _WIN32 |
||||
#include <windows.h> |
||||
#endif |
||||
|
||||
namespace godot { |
||||
|
||||
class TypePhpOceanBridge : public Node { |
||||
GDCLASS(TypePhpOceanBridge, Node) |
||||
|
||||
using FnInit = int(__cdecl *)(); |
||||
using FnCount = int(__cdecl *)(); |
||||
using FnDoubleAt = double(__cdecl *)(int); |
||||
using FnIntAt = int(__cdecl *)(int); |
||||
using FnWeather = int(__cdecl *)(int, double); |
||||
|
||||
#ifdef _WIN32 |
||||
HMODULE library = nullptr; |
||||
#endif |
||||
FnInit ocean_init = nullptr; |
||||
FnCount island_count = nullptr; |
||||
FnDoubleAt island_x = nullptr; |
||||
FnDoubleAt island_z = nullptr; |
||||
FnDoubleAt island_radius = nullptr; |
||||
FnDoubleAt island_height = nullptr; |
||||
FnDoubleAt island_seed = nullptr; |
||||
FnCount marker_count = nullptr; |
||||
FnDoubleAt marker_x = nullptr; |
||||
FnDoubleAt marker_z = nullptr; |
||||
FnIntAt marker_type = nullptr; |
||||
FnDoubleAt marker_size = nullptr; |
||||
FnWeather choose_weather = nullptr; |
||||
|
||||
protected: |
||||
static void _bind_methods(); |
||||
|
||||
public: |
||||
TypePhpOceanBridge(); |
||||
~TypePhpOceanBridge(); |
||||
|
||||
Array get_islands(double radius); |
||||
Array get_markers(double radius); |
||||
int choose_next_weather(int current, double roll); |
||||
|
||||
private: |
||||
bool ensure_loaded(); |
||||
}; |
||||
|
||||
} // namespace godot
|
||||
@ -0,0 +1,94 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
const OCEAN_WEATHER_SUNNY = 0; |
||||
const OCEAN_WEATHER_CLOUDY = 1; |
||||
const OCEAN_WEATHER_RAIN = 2; |
||||
|
||||
function ocean_hash01(int $x, int $z, int $salt): float |
||||
{ |
||||
$n = sin($x * 127.1 + $z * 311.7 + $salt * 74.7) * 43758.5453123; |
||||
return $n - floor($n); |
||||
} |
||||
|
||||
function ocean_island_count(): int |
||||
{ |
||||
return 18; |
||||
} |
||||
|
||||
function ocean_island_x(int $index): float |
||||
{ |
||||
$angle = $index * 2.39996323 + ocean_hash01($index, 11, 31) * 0.62; |
||||
$ring = 190.0 + ($index % 5) * 145.0 + ocean_hash01($index, 13, 37) * 120.0; |
||||
return cos($angle) * $ring; |
||||
} |
||||
|
||||
function ocean_island_z(int $index): float |
||||
{ |
||||
$angle = $index * 2.39996323 + ocean_hash01($index, 11, 31) * 0.62; |
||||
$ring = 190.0 + ($index % 5) * 145.0 + ocean_hash01($index, 13, 37) * 120.0; |
||||
return sin($angle) * $ring; |
||||
} |
||||
|
||||
function ocean_island_radius(int $index): float |
||||
{ |
||||
return 36.0 + ocean_hash01($index, 3, 11) * 58.0; |
||||
} |
||||
|
||||
function ocean_island_height(int $index): float |
||||
{ |
||||
return 10.0 + ocean_hash01($index, 7, 13) * 26.0; |
||||
} |
||||
|
||||
function ocean_island_seed(int $index): float |
||||
{ |
||||
return 17.0 + $index * 31.0; |
||||
} |
||||
|
||||
function ocean_marker_count(): int |
||||
{ |
||||
return 12; |
||||
} |
||||
|
||||
function ocean_marker_x(int $index): float |
||||
{ |
||||
$ring = 160.0 + ($index % 4) * 150.0; |
||||
$angle = $index * 2.39996323 + ocean_hash01($index, 1, 5) * 0.35; |
||||
return cos($angle) * $ring; |
||||
} |
||||
|
||||
function ocean_marker_z(int $index): float |
||||
{ |
||||
$ring = 160.0 + ($index % 4) * 150.0; |
||||
$angle = $index * 2.39996323 + ocean_hash01($index, 1, 5) * 0.35; |
||||
return sin($angle) * $ring; |
||||
} |
||||
|
||||
function ocean_marker_type(int $index): int |
||||
{ |
||||
$roll = ocean_hash01($index, 9, 17); |
||||
if ($roll > 0.78) { |
||||
return 3; |
||||
} |
||||
if ($roll > 0.48) { |
||||
return 2; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
function ocean_marker_size(int $index): float |
||||
{ |
||||
return 1.6 + ocean_hash01($index, 4, 23) * 5.8; |
||||
} |
||||
|
||||
function ocean_choose_next_weather(int $current, float $roll): int |
||||
{ |
||||
if ($current === OCEAN_WEATHER_SUNNY) { |
||||
return $roll < 0.58 ? OCEAN_WEATHER_CLOUDY : OCEAN_WEATHER_RAIN; |
||||
} |
||||
if ($current === OCEAN_WEATHER_CLOUDY) { |
||||
return $roll < 0.42 ? OCEAN_WEATHER_SUNNY : OCEAN_WEATHER_RAIN; |
||||
} |
||||
return $roll < 0.58 ? OCEAN_WEATHER_CLOUDY : OCEAN_WEATHER_SUNNY; |
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
; Engine configuration file. |
||||
; It's best edited using the editor UI and not directly. |
||||
|
||||
config_version=5 |
||||
|
||||
[application] |
||||
|
||||
config/name="TypePHP Ocean Odyssey" |
||||
run/main_scene="res://scenes/main.tscn" |
||||
config/features=PackedStringArray("4.7") |
||||
|
||||
[display] |
||||
|
||||
window/size/viewport_width=1280 |
||||
window/size/viewport_height=720 |
||||
|
||||
[input] |
||||
|
||||
move_forward={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
move_back={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
move_left={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
move_right={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":68,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
toggle_mouse={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
boost={ |
||||
"deadzone": 0.5, |
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194325,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] |
||||
} |
||||
|
||||
[rendering] |
||||
|
||||
renderer/rendering_method="gl_compatibility" |
||||
lights_and_shadows/directional_shadow/size=4096 |
||||
anti_aliasing/quality/msaa_3d=2 |
||||
anti_aliasing/quality/screen_space_aa=0 |
||||
environment/volumetric_fog/use_filter=true |
||||
@ -0,0 +1,87 @@ |
||||
[gd_scene load_steps=5 format=3 uid="uid://typephp_ocean_odyssey"] |
||||
|
||||
[ext_resource type="Script" path="res://scripts/ocean_world.gd" id="1_world"] |
||||
|
||||
[sub_resource type="Environment" id="Environment_1"] |
||||
background_mode = 1 |
||||
background_color = Color(0.52, 0.72, 0.92, 1) |
||||
ambient_light_source = 2 |
||||
ambient_light_color = Color(0.55, 0.64, 0.72, 1) |
||||
ambient_light_energy = 0.95 |
||||
reflected_light_source = 2 |
||||
tonemap_mode = 2 |
||||
tonemap_exposure = 1.08 |
||||
tonemap_white = 3.2 |
||||
ssr_enabled = false |
||||
ssr_max_steps = 32 |
||||
ssr_fade_in = 0.08 |
||||
ssr_fade_out = 2.4 |
||||
ssao_enabled = true |
||||
ssao_radius = 1.25 |
||||
ssao_intensity = 0.82 |
||||
glow_enabled = true |
||||
glow_intensity = 0.42 |
||||
glow_bloom = 0.18 |
||||
fog_enabled = true |
||||
fog_light_color = Color(0.50, 0.70, 0.92, 1) |
||||
fog_density = 0.003 |
||||
volumetric_fog_enabled = false |
||||
volumetric_fog_density = 0.008 |
||||
volumetric_fog_albedo = Color(0.72, 0.82, 0.92, 1) |
||||
|
||||
[sub_resource type="Curve" id="RainAmountCurve"] |
||||
_data = [Vector2(0, 0.85), 0.0, 0.0, 0, 0, Vector2(1, 0.25), 0.0, 0.0, 0, 0] |
||||
point_count = 2 |
||||
|
||||
[sub_resource type="ParticleProcessMaterial" id="RainProcess"] |
||||
emission_shape = 2 |
||||
emission_box_extents = Vector3(130, 1, 130) |
||||
direction = Vector3(0.18, -1, 0.08) |
||||
spread = 6.0 |
||||
gravity = Vector3(4, -42, 1.5) |
||||
initial_velocity_min = 24.0 |
||||
initial_velocity_max = 34.0 |
||||
scale_min = 0.55 |
||||
scale_max = 1.4 |
||||
scale_curve = SubResource("RainAmountCurve") |
||||
color = Color(0.7, 0.86, 1, 0.42) |
||||
|
||||
[sub_resource type="BoxMesh" id="RainDropMesh"] |
||||
size = Vector3(0.012, 0.62, 0.012) |
||||
|
||||
[node name="Main" type="Node3D"] |
||||
script = ExtResource("1_world") |
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] |
||||
environment = SubResource("Environment_1") |
||||
|
||||
[node name="Sun" type="DirectionalLight3D" parent="."] |
||||
transform = Transform3D(0.731354, -0.328178, 0.598388, 0, 0.876156, 0.482028, -0.681998, -0.352584, 0.640786, 0, 28, 0) |
||||
light_color = Color(1, 0.92, 0.78, 1) |
||||
light_energy = 3.2 |
||||
shadow_enabled = true |
||||
directional_shadow_mode = 2 |
||||
directional_shadow_max_distance = 240.0 |
||||
shadow_blur = 1.1 |
||||
|
||||
[node name="Moon" type="DirectionalLight3D" parent="."] |
||||
transform = Transform3D(-0.866025, -0.25, -0.433013, 0, 0.866025, -0.5, 0.5, -0.433013, -0.75, 0, 28, 0) |
||||
light_color = Color(0.46, 0.58, 0.92, 1) |
||||
light_energy = 0.0 |
||||
|
||||
[node name="CameraRig" type="Node3D" parent="."] |
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraRig"] |
||||
transform = Transform3D(1, 0, 0, 0, 0.956305, 0.292372, 0, -0.292372, 0.956305, 0, 8.5, 24) |
||||
current = true |
||||
fov = 68.0 |
||||
near = 0.05 |
||||
far = 1000.0 |
||||
|
||||
[node name="Rain" type="GPUParticles3D" parent="."] |
||||
amount = 3000 |
||||
lifetime = 1.85 |
||||
preprocess = 1.0 |
||||
visibility_aabb = AABB(-110, -70, -110, 220, 140, 220) |
||||
process_material = SubResource("RainProcess") |
||||
draw_pass_1 = SubResource("RainDropMesh") |
||||
@ -0,0 +1,869 @@ |
||||
extends Node3D |
||||
|
||||
const OCEAN_SIZE := 960.0 |
||||
const ISLAND_GRID := 44 |
||||
const CLOUD_LAYER_COUNT := 3 |
||||
const WEATHER_SUNNY := 0 |
||||
const WEATHER_CLOUDY := 1 |
||||
const WEATHER_RAIN := 2 |
||||
const CAMERA_MIN_PITCH := deg_to_rad(-10.0) |
||||
const CAMERA_MAX_PITCH := deg_to_rad(28.0) |
||||
|
||||
var boat_position := Vector3.ZERO |
||||
var boat_yaw := 0.0 |
||||
var boat_velocity := Vector3.ZERO |
||||
var camera_yaw := 0.0 |
||||
var camera_pitch := deg_to_rad(4.0) |
||||
var day_time := 0.27 |
||||
var weather := WEATHER_SUNNY |
||||
var target_weather := WEATHER_CLOUDY |
||||
var weather_mix := 0.0 |
||||
var next_weather_change := 14.0 |
||||
var rain_amount := 0.0 |
||||
var mouse_sensitivity := 0.0022 |
||||
var bridge: Object |
||||
|
||||
var ocean_material: ShaderMaterial |
||||
var island_material: ShaderMaterial |
||||
var sky_material: ShaderMaterial |
||||
var cloud_materials: Array[ShaderMaterial] = [] |
||||
var island_root: Node3D |
||||
var marker_root: Node3D |
||||
var boat: Node3D |
||||
var sail_material: StandardMaterial3D |
||||
var ocean_tiles: Array[MeshInstance3D] = [] |
||||
var sky_dome: MeshInstance3D |
||||
|
||||
@onready var camera_rig: Node3D = $CameraRig |
||||
@onready var camera: Camera3D = $CameraRig/Camera3D |
||||
@onready var sun: DirectionalLight3D = $Sun |
||||
@onready var moon: DirectionalLight3D = $Moon |
||||
@onready var rain: GPUParticles3D = $Rain |
||||
@onready var world_environment: WorldEnvironment = $WorldEnvironment |
||||
|
||||
|
||||
func _ready() -> void: |
||||
_ensure_input_map() |
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED |
||||
bridge = _try_create_bridge() |
||||
_create_materials() |
||||
_create_sky() |
||||
_create_ocean() |
||||
_create_islands() |
||||
_create_markers() |
||||
_create_boat() |
||||
_update_environment(0.0) |
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void: |
||||
if event.is_action_pressed("toggle_mouse"): |
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED |
||||
get_viewport().set_input_as_handled() |
||||
elif event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: |
||||
camera_yaw -= event.relative.x * mouse_sensitivity |
||||
camera_pitch = clampf(camera_pitch - event.relative.y * mouse_sensitivity, CAMERA_MIN_PITCH, CAMERA_MAX_PITCH) |
||||
get_viewport().set_input_as_handled() |
||||
|
||||
|
||||
func _process(delta: float) -> void: |
||||
var dt := clampf(delta, 0.001, 0.05) |
||||
_update_boat(dt) |
||||
_update_weather(dt) |
||||
_update_environment(dt) |
||||
_update_camera(dt) |
||||
_update_ocean_tiles() |
||||
rain.position = boat_position + Vector3(0.0, 58.0, 0.0) |
||||
|
||||
|
||||
func _try_create_bridge() -> Object: |
||||
if not ClassDB.class_exists("TypePhpOceanBridge"): |
||||
return null |
||||
var obj: Object = ClassDB.instantiate("TypePhpOceanBridge") |
||||
if obj is Node: |
||||
add_child(obj) |
||||
return obj |
||||
|
||||
|
||||
func _create_materials() -> void: |
||||
ocean_material = _make_ocean_material() |
||||
island_material = _make_island_material() |
||||
sky_material = _make_sky_material() |
||||
sail_material = StandardMaterial3D.new() |
||||
sail_material.albedo_color = Color(0.94, 0.9, 0.78) |
||||
sail_material.roughness = 0.82 |
||||
sail_material.metallic = 0.0 |
||||
sail_material.cull_mode = BaseMaterial3D.CULL_DISABLED |
||||
|
||||
|
||||
func _make_ocean_material() -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode specular_schlick_ggx, cull_disabled; |
||||
|
||||
uniform vec4 deep_color : source_color = vec4(0.002, 0.026, 0.105, 1.0); |
||||
uniform vec4 mid_color : source_color = vec4(0.006, 0.100, 0.290, 1.0); |
||||
uniform vec4 shallow_color : source_color = vec4(0.018, 0.260, 0.560, 1.0); |
||||
uniform vec4 storm_color : source_color = vec4(0.006, 0.026, 0.065, 1.0); |
||||
uniform float storm = 0.0; |
||||
uniform float rain = 0.0; |
||||
uniform float time_scale = 1.0; |
||||
uniform vec3 sun_dir = vec3(-0.45, 0.72, -0.54); |
||||
uniform vec3 boat_pos = vec3(0.0, 0.0, 0.0); |
||||
uniform vec2 boat_forward = vec2(0.0, -1.0); |
||||
uniform float boat_speed = 0.0; |
||||
|
||||
float wave(vec2 p, vec2 d, float amp, float freq, float speed) { |
||||
float phase = dot(p, normalize(d)) * freq + TIME * speed * time_scale; |
||||
return sin(phase) * amp * (1.0 + storm * 0.55); |
||||
} |
||||
|
||||
float hash(vec2 p) { |
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); |
||||
} |
||||
|
||||
float noise(vec2 p) { |
||||
vec2 i = floor(p); |
||||
vec2 f = fract(p); |
||||
f = f * f * (3.0 - 2.0 * f); |
||||
float a = hash(i); |
||||
float b = hash(i + vec2(1.0, 0.0)); |
||||
float c = hash(i + vec2(0.0, 1.0)); |
||||
float d = hash(i + vec2(1.0, 1.0)); |
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); |
||||
} |
||||
|
||||
float height_at(vec2 p) { |
||||
float h = 0.0; |
||||
h += wave(p, vec2(0.92, 0.28), 0.72, 0.035, 0.54); |
||||
h += wave(p, vec2(0.38, 1.0), 0.38, 0.070, 0.86); |
||||
h += wave(p, vec2(-0.68, 0.55), 0.16, 0.155, 1.58); |
||||
h += wave(p, vec2(0.24, -0.92), 0.045, 0.410, 2.45); |
||||
return h; |
||||
} |
||||
|
||||
void vertex() { |
||||
VERTEX.y += height_at(VERTEX.xz + NODE_POSITION_WORLD.xz) * 0.035; |
||||
} |
||||
|
||||
void fragment() { |
||||
vec2 p = (VERTEX.xz + NODE_POSITION_WORLD.xz); |
||||
vec2 d0 = normalize(vec2(0.92, 0.28)); |
||||
vec2 d1 = normalize(vec2(0.38, 1.0)); |
||||
vec2 d2 = normalize(vec2(-0.68, 0.55)); |
||||
vec2 d3 = normalize(vec2(0.24, -0.92)); |
||||
float p0 = dot(p, d0) * 0.035 + TIME * 0.54; |
||||
float p1 = dot(p, d1) * 0.070 + TIME * 0.86; |
||||
float p2 = dot(p, d2) * 0.155 + TIME * 1.58; |
||||
float p3 = dot(p, d3) * 0.410 + TIME * 2.45; |
||||
float p4 = dot(p, normalize(vec2(0.78, -0.18))) * 0.82 + TIME * 3.35; |
||||
float p5 = dot(p, normalize(vec2(-0.22, 0.96))) * 1.37 + TIME * 4.25; |
||||
float h = sin(p0) * 0.72 + sin(p1) * 0.38 + sin(p2) * 0.16 + sin(p3) * 0.045 + sin(p4) * 0.018 + sin(p5) * 0.010; |
||||
vec2 micro = vec2(noise(p * 0.085 + vec2(TIME * 0.08, 3.7)), noise(p * 0.095 + vec2(11.4, TIME * 0.07))) - vec2(0.5); |
||||
vec2 slope = d0 * cos(p0) * 0.025 + d1 * cos(p1) * 0.027 + d2 * cos(p2) * 0.025 + d3 * cos(p3) * 0.018 + normalize(vec2(0.78, -0.18)) * cos(p4) * 0.015 + normalize(vec2(-0.22, 0.96)) * cos(p5) * 0.010 + micro * 0.018; |
||||
float view_distance = length(VERTEX.xz); |
||||
float near_detail = 1.0 - smoothstep(280.0, 820.0, view_distance); |
||||
NORMAL = normalize(vec3(-slope.x * mix(10.0, 19.0, near_detail), 1.0, -slope.y * mix(10.0, 19.0, near_detail))); |
||||
vec3 view_dir = normalize(VIEW); |
||||
vec3 light_dir = normalize(sun_dir); |
||||
vec3 half_dir = normalize(light_dir - view_dir); |
||||
float facing = pow(1.0 - clamp(dot(view_dir, NORMAL), 0.0, 1.0), 4.0); |
||||
float chop = clamp(length(slope) * 8.0 + abs(h) * 0.18 + storm * 0.36, 0.0, 1.0); |
||||
float sky_reflect = pow(facing, 0.85) * (0.10 - rain * 0.05); |
||||
float long_bands = sin(dot(p, vec2(0.010, 0.018)) + TIME * 0.16) * 0.5 + 0.5; |
||||
vec3 sea = mix(deep_color.rgb, mid_color.rgb, 0.30 + chop * 0.16 + long_bands * 0.08); |
||||
sea = mix(sea, shallow_color.rgb, smoothstep(0.80, 1.22, h) * 0.20 * near_detail); |
||||
sea = mix(sea, storm_color.rgb, storm * 0.72); |
||||
sea += vec3(0.025, 0.075, 0.14) * sky_reflect; |
||||
float foam_lines = smoothstep(0.84, 1.0, sin(dot(p, vec2(0.055, 0.072)) + TIME * 1.35) * 0.5 + 0.5); |
||||
float sun_glint = pow(max(dot(NORMAL, half_dir), 0.0), 110.0) * smoothstep(0.05, 0.65, light_dir.y) * (1.0 - rain * 0.75); |
||||
float sparkle_a = smoothstep(0.88, 1.0, sin(dot(p, vec2(0.72, -0.18)) + TIME * 3.4) * 0.5 + 0.5); |
||||
float sparkle_b = smoothstep(0.90, 1.0, sin(dot(p, vec2(-0.24, 1.18)) + TIME * 4.7) * 0.5 + 0.5); |
||||
float grid_breakup = sin(p.x * 0.173 + p.y * 0.097 + TIME * 0.41) * 0.5 + 0.5; |
||||
float glitter = (sparkle_a * 0.50 + sparkle_b * 0.34 + grid_breakup * 0.16) * smoothstep(0.38, 0.92, chop) * (1.0 - storm * 0.55) * near_detail; |
||||
float crest_foam = smoothstep(0.80, 1.0, chop) * foam_lines * (0.040 + storm * 0.22) * near_detail; |
||||
vec2 to_boat = p - boat_pos.xz; |
||||
vec2 forward = normalize(boat_forward); |
||||
vec2 right = vec2(forward.y, -forward.x); |
||||
float behind = smoothstep(3.0, -18.0, dot(to_boat, forward)); |
||||
float side = abs(dot(normalize(to_boat + vec2(0.001)), right)); |
||||
float wake_wing = smoothstep(0.72, 0.46, abs(side - 0.55)); |
||||
float wake_fade = smoothstep(46.0, 3.0, length(to_boat)); |
||||
float wake_core = smoothstep(0.18, 0.02, abs(dot(to_boat, right))) * smoothstep(22.0, 3.0, length(to_boat)); |
||||
float wake = (wake_wing * 0.88 + wake_core * 0.32) * wake_fade * behind * clamp(boat_speed / 8.0, 0.0, 1.0); |
||||
float foam = clamp(crest_foam + wake * 0.72, 0.0, 1.0); |
||||
vec3 glint_color = vec3(0.80, 0.93, 1.0) * (sun_glint * 0.95 + glitter * 0.10); |
||||
float horizon_blend = smoothstep(520.0, 1180.0, view_distance); |
||||
vec3 horizon_water = vec3(0.050, 0.150, 0.310); |
||||
ALBEDO = mix(mix(sea, vec3(0.72, 0.88, 0.94), foam) + glint_color, horizon_water, horizon_blend * (0.22 + storm * 0.20)); |
||||
ROUGHNESS = mix(0.070, 0.22, storm + rain * 0.45); |
||||
SPECULAR = mix(0.92, 0.45, rain); |
||||
RIM = facing * 0.28; |
||||
RIM_TINT = 0.28; |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
return material |
||||
|
||||
|
||||
func _make_island_material() -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode cull_back, diffuse_burley, specular_schlick_ggx; |
||||
|
||||
uniform vec4 sand : source_color = vec4(0.76, 0.62, 0.36, 1.0); |
||||
uniform vec4 soil : source_color = vec4(0.62, 0.45, 0.22, 1.0); |
||||
uniform vec4 rock : source_color = vec4(0.46, 0.35, 0.21, 1.0); |
||||
uniform vec4 grass : source_color = vec4(0.12, 0.34, 0.12, 1.0); |
||||
|
||||
float patch_noise(vec2 p) { |
||||
return fract(sin(dot(floor(p), vec2(127.1, 311.7))) * 43758.5453); |
||||
} |
||||
|
||||
void fragment() { |
||||
float slope = 1.0 - clamp(NORMAL.y, 0.0, 1.0); |
||||
float height = VERTEX.y; |
||||
float beach = smoothstep(0.0, 1.8, height); |
||||
vec3 color = mix(sand.rgb, soil.rgb, beach); |
||||
color = mix(color, rock.rgb, smoothstep(0.38, 0.78, slope)); |
||||
float vegetation_noise = patch_noise(VERTEX.xz * 0.11); |
||||
float vegetation = smoothstep(2.7, 7.0, height) * smoothstep(0.66, 0.18, slope) * smoothstep(0.36, 0.74, vegetation_noise); |
||||
color = mix(color, grass.rgb, vegetation * 0.78); |
||||
ALBEDO = color; |
||||
ROUGHNESS = 0.88; |
||||
SPECULAR = 0.25; |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
return material |
||||
|
||||
|
||||
func _make_sky_material() -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode unshaded, cull_front, depth_draw_never, fog_disabled; |
||||
|
||||
uniform float day_time = 0.28; |
||||
uniform float storm = 0.0; |
||||
uniform vec3 sun_dir = vec3(-0.45, 0.72, -0.54); |
||||
|
||||
float hash(vec2 p) { |
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); |
||||
} |
||||
|
||||
void fragment() { |
||||
vec3 v = normalize(VIEW); |
||||
float up = clamp(v.y * 0.5 + 0.5, 0.0, 1.0); |
||||
float daylight = smoothstep(-0.16, 0.18, sun_dir.y); |
||||
vec3 day_top = vec3(0.10, 0.46, 0.92); |
||||
vec3 day_horizon = vec3(0.70, 0.88, 1.0); |
||||
vec3 dusk_top = vec3(0.08, 0.12, 0.30); |
||||
vec3 dusk_horizon = vec3(1.0, 0.44, 0.22); |
||||
vec3 night_top = vec3(0.01, 0.018, 0.055); |
||||
vec3 night_horizon = vec3(0.05, 0.08, 0.16); |
||||
float dusk = 1.0 - smoothstep(0.08, 0.42, abs(sun_dir.y)); |
||||
vec3 top = mix(night_top, day_top, daylight); |
||||
vec3 horizon = mix(night_horizon, day_horizon, daylight); |
||||
top = mix(top, dusk_top, dusk * 0.5); |
||||
horizon = mix(horizon, dusk_horizon, dusk); |
||||
vec3 sky = mix(horizon, top, smoothstep(0.02, 0.92, up)); |
||||
sky += vec3(0.04, 0.10, 0.16) * smoothstep(0.36, 0.88, up) * daylight; |
||||
sky = mix(sky, vec3(0.09, 0.11, 0.13), storm * 0.78); |
||||
float sun = pow(max(dot(normalize(-v), normalize(sun_dir)), 0.0), 640.0) * daylight; |
||||
float glow = pow(max(dot(normalize(-v), normalize(sun_dir)), 0.0), 14.0) * daylight; |
||||
float stars = step(0.996, hash(floor(v.xz * 460.0))) * (1.0 - daylight) * smoothstep(0.18, 0.8, up); |
||||
ALBEDO = sky + vec3(1.0, 0.76, 0.35) * sun * 4.0 + vec3(1.0, 0.45, 0.20) * glow * dusk * 0.55 + vec3(stars); |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
return material |
||||
|
||||
|
||||
func _create_sky() -> void: |
||||
sky_dome = MeshInstance3D.new() |
||||
sky_dome.name = "SkyDome" |
||||
var sphere := SphereMesh.new() |
||||
sphere.radius = 900.0 |
||||
sphere.height = 900.0 |
||||
sphere.radial_segments = 64 |
||||
sphere.rings = 32 |
||||
sky_dome.mesh = sphere |
||||
sky_dome.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF |
||||
sky_dome.material_override = sky_material |
||||
add_child(sky_dome) |
||||
|
||||
for i in range(CLOUD_LAYER_COUNT): |
||||
var cloud := MeshInstance3D.new() |
||||
cloud.name = "CloudLayer%d" % i |
||||
var mesh := PlaneMesh.new() |
||||
mesh.size = Vector2(1100.0 + i * 120.0, 1100.0 + i * 120.0) |
||||
mesh.subdivide_width = 1 |
||||
mesh.subdivide_depth = 1 |
||||
cloud.mesh = mesh |
||||
cloud.position = Vector3((i - 2) * 80.0, 84.0 + i * 13.0, -70.0 + i * 45.0) |
||||
cloud.rotation.x = deg_to_rad(4.0 + i * 1.5) |
||||
var material := _make_cloud_material(i) |
||||
cloud_materials.append(material) |
||||
cloud.material_override = material |
||||
add_child(cloud) |
||||
_create_horizon_clouds() |
||||
_create_cirrus_clouds() |
||||
|
||||
|
||||
func _create_horizon_clouds() -> void: |
||||
var material := _make_horizon_cloud_material() |
||||
cloud_materials.append(material) |
||||
for i in range(8): |
||||
var cloud := MeshInstance3D.new() |
||||
cloud.name = "HorizonCloudBand%d" % i |
||||
var mesh := PlaneMesh.new() |
||||
mesh.size = Vector2(320.0, 76.0) |
||||
cloud.mesh = mesh |
||||
var angle := TAU * float(i) / 8.0 |
||||
var pos := Vector3(sin(angle) * 620.0, 64.0 + sin(angle * 2.0) * 10.0, cos(angle) * 620.0) |
||||
cloud.position = pos |
||||
cloud.look_at_from_position(pos, Vector3(0.0, 58.0, 0.0), Vector3.UP) |
||||
cloud.rotation.x += deg_to_rad(-4.0) |
||||
cloud.material_override = material |
||||
add_child(cloud) |
||||
|
||||
|
||||
func _create_cirrus_clouds() -> void: |
||||
var material := _make_cirrus_cloud_material() |
||||
cloud_materials.append(material) |
||||
for i in range(3): |
||||
var cloud := MeshInstance3D.new() |
||||
cloud.name = "CirrusCloud%d" % i |
||||
var mesh := PlaneMesh.new() |
||||
mesh.size = Vector2(920.0, 210.0) |
||||
cloud.mesh = mesh |
||||
cloud.position = Vector3((i - 1) * 170.0, 210.0 + i * 16.0, -260.0 + i * 130.0) |
||||
cloud.rotation = Vector3(deg_to_rad(12.0), deg_to_rad(8.0 + i * 9.0), deg_to_rad(-4.0 + i * 3.0)) |
||||
cloud.material_override = material |
||||
add_child(cloud) |
||||
|
||||
|
||||
func _make_cloud_material(layer: int) -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled; |
||||
|
||||
uniform float coverage = 0.42; |
||||
uniform float density = 0.64; |
||||
uniform float storm = 0.0; |
||||
uniform float speed = 0.018; |
||||
uniform vec4 bright : source_color = vec4(1.0, 1.0, 0.96, 0.68); |
||||
uniform vec4 dark : source_color = vec4(0.33, 0.39, 0.46, 0.82); |
||||
|
||||
float noise(vec2 p) { |
||||
vec2 i = floor(p); |
||||
vec2 f = fract(p); |
||||
f = f * f * (3.0 - 2.0 * f); |
||||
float a = fract(sin(dot(i, vec2(127.1, 311.7))) * 43758.5453); |
||||
float b = fract(sin(dot(i + vec2(1, 0), vec2(127.1, 311.7))) * 43758.5453); |
||||
float c = fract(sin(dot(i + vec2(0, 1), vec2(127.1, 311.7))) * 43758.5453); |
||||
float d = fract(sin(dot(i + vec2(1, 1), vec2(127.1, 311.7))) * 43758.5453); |
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); |
||||
} |
||||
|
||||
float fbm(vec2 p) { |
||||
float v = 0.0; |
||||
float a = 0.5; |
||||
for (int i = 0; i < 5; i++) { |
||||
v += noise(p) * a; |
||||
p *= 2.17; |
||||
a *= 0.52; |
||||
} |
||||
return v; |
||||
} |
||||
|
||||
void fragment() { |
||||
vec2 uv = UV * 5.0 + vec2(TIME * speed, TIME * speed * 0.23); |
||||
float base = fbm(uv); |
||||
float detail = fbm(uv * 3.0 + vec2(17.0, 9.0)); |
||||
float edge = smoothstep(coverage, 1.0, base * 0.82 + detail * 0.26); |
||||
float alpha = edge * density * (0.55 + storm * 0.42); |
||||
vec3 color = mix(bright.rgb, dark.rgb, storm * 0.72 + (1.0 - detail) * 0.18); |
||||
ALBEDO = color; |
||||
ALPHA = alpha; |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
material.set_shader_parameter("coverage", 0.45 + layer * 0.035) |
||||
material.set_shader_parameter("density", 0.36 + layer * 0.065) |
||||
material.set_shader_parameter("speed", 0.012 + layer * 0.003) |
||||
return material |
||||
|
||||
|
||||
func _make_horizon_cloud_material() -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled; |
||||
|
||||
uniform float storm = 0.0; |
||||
uniform vec4 bright : source_color = vec4(1.0, 0.98, 0.91, 0.82); |
||||
uniform vec4 shade : source_color = vec4(0.52, 0.66, 0.78, 0.72); |
||||
|
||||
float hash(vec2 p) { |
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); |
||||
} |
||||
|
||||
float noise(vec2 p) { |
||||
vec2 i = floor(p); |
||||
vec2 f = fract(p); |
||||
f = f * f * (3.0 - 2.0 * f); |
||||
float a = hash(i); |
||||
float b = hash(i + vec2(1.0, 0.0)); |
||||
float c = hash(i + vec2(0.0, 1.0)); |
||||
float d = hash(i + vec2(1.0, 1.0)); |
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); |
||||
} |
||||
|
||||
float fbm(vec2 p) { |
||||
float v = 0.0; |
||||
float a = 0.55; |
||||
for (int i = 0; i < 4; i++) { |
||||
v += noise(p) * a; |
||||
p *= 2.1; |
||||
a *= 0.5; |
||||
} |
||||
return v; |
||||
} |
||||
|
||||
void fragment() { |
||||
vec2 uv = UV; |
||||
float mound = smoothstep(0.12, 0.36, uv.y) * smoothstep(1.0, 0.44, uv.y); |
||||
float n = fbm(vec2(uv.x * 8.0 + TIME * 0.006, uv.y * 3.2)); |
||||
float shape = smoothstep(0.34, 0.72, n + mound * 0.42); |
||||
float horizon_fade = smoothstep(0.02, 0.18, uv.y) * smoothstep(0.96, 0.48, uv.y); |
||||
vec3 color = mix(shade.rgb, bright.rgb, smoothstep(0.35, 0.86, uv.y + n * 0.22)); |
||||
color = mix(color, vec3(0.42, 0.48, 0.54), storm * 0.72); |
||||
ALBEDO = color; |
||||
ALPHA = shape * horizon_fade * mix(0.70, 0.88, storm); |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
return material |
||||
|
||||
|
||||
func _make_cirrus_cloud_material() -> ShaderMaterial: |
||||
var shader := Shader.new() |
||||
shader.code = """ |
||||
shader_type spatial; |
||||
render_mode blend_mix, depth_draw_never, cull_disabled, unshaded, fog_disabled; |
||||
|
||||
uniform float storm = 0.0; |
||||
|
||||
float hash(vec2 p) { |
||||
return fract(sin(dot(p, vec2(269.5, 183.3))) * 43758.5453); |
||||
} |
||||
|
||||
float noise(vec2 p) { |
||||
vec2 i = floor(p); |
||||
vec2 f = fract(p); |
||||
f = f * f * (3.0 - 2.0 * f); |
||||
float a = hash(i); |
||||
float b = hash(i + vec2(1.0, 0.0)); |
||||
float c = hash(i + vec2(0.0, 1.0)); |
||||
float d = hash(i + vec2(1.0, 1.0)); |
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); |
||||
} |
||||
|
||||
void fragment() { |
||||
vec2 uv = UV; |
||||
vec2 stretched = vec2(uv.x * 13.0 + uv.y * 4.5 + TIME * 0.01, uv.y * 2.3); |
||||
float fibers = noise(stretched) * 0.62 + noise(stretched * 2.4 + vec2(3.1, 7.4)) * 0.30; |
||||
float long_shape = smoothstep(0.06, 0.22, uv.y) * smoothstep(0.98, 0.34, uv.y); |
||||
float torn_edges = smoothstep(0.48, 0.78, fibers); |
||||
float alpha = torn_edges * long_shape * (1.0 - storm * 0.72) * 0.54; |
||||
ALBEDO = mix(vec3(0.82, 0.91, 1.0), vec3(1.0, 0.98, 0.92), smoothstep(0.36, 0.86, uv.y)); |
||||
ALPHA = alpha; |
||||
} |
||||
""" |
||||
var material := ShaderMaterial.new() |
||||
material.shader = shader |
||||
return material |
||||
|
||||
|
||||
func _create_ocean() -> void: |
||||
var mesh := PlaneMesh.new() |
||||
mesh.size = Vector2(OCEAN_SIZE, OCEAN_SIZE) |
||||
mesh.subdivide_width = 16 |
||||
mesh.subdivide_depth = 16 |
||||
for x in range(-1, 2): |
||||
for z in range(-1, 2): |
||||
var tile := MeshInstance3D.new() |
||||
tile.name = "OceanTile_%d_%d" % [x, z] |
||||
tile.mesh = mesh |
||||
tile.material_override = ocean_material |
||||
tile.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF |
||||
add_child(tile) |
||||
ocean_tiles.append(tile) |
||||
_update_ocean_tiles() |
||||
|
||||
|
||||
func _update_ocean_tiles() -> void: |
||||
var center_x: float = floor(boat_position.x / OCEAN_SIZE) * OCEAN_SIZE |
||||
var center_z: float = floor(boat_position.z / OCEAN_SIZE) * OCEAN_SIZE |
||||
var index := 0 |
||||
for x in range(-1, 2): |
||||
for z in range(-1, 2): |
||||
var tile := ocean_tiles[index] |
||||
tile.position = Vector3(center_x + float(x) * OCEAN_SIZE, 0.0, center_z + float(z) * OCEAN_SIZE) |
||||
index += 1 |
||||
|
||||
|
||||
func _create_islands() -> void: |
||||
island_root = Node3D.new() |
||||
island_root.name = "Islands" |
||||
add_child(island_root) |
||||
var islands: Array = _bridge_array("get_islands", [1180.0]) |
||||
if islands.is_empty(): |
||||
islands = _fallback_islands() |
||||
for island in islands: |
||||
if island is Dictionary: |
||||
_add_island(island) |
||||
|
||||
|
||||
func _fallback_islands() -> Array: |
||||
return [ |
||||
{"x": 170.0, "z": -210.0, "radius": 58.0, "height": 20.0, "seed": 11.0}, |
||||
{"x": -260.0, "z": 120.0, "radius": 42.0, "height": 14.0, "seed": 27.0}, |
||||
{"x": 420.0, "z": 310.0, "radius": 78.0, "height": 28.0, "seed": 43.0}, |
||||
{"x": -520.0, "z": -390.0, "radius": 64.0, "height": 22.0, "seed": 61.0}, |
||||
{"x": 720.0, "z": -110.0, "radius": 52.0, "height": 18.0, "seed": 83.0}, |
||||
{"x": -810.0, "z": 470.0, "radius": 70.0, "height": 31.0, "seed": 97.0}, |
||||
{"x": 120.0, "z": 820.0, "radius": 46.0, "height": 16.0, "seed": 109.0}, |
||||
{"x": -650.0, "z": -40.0, "radius": 88.0, "height": 34.0, "seed": 131.0}, |
||||
{"x": 910.0, "z": 560.0, "radius": 61.0, "height": 24.0, "seed": 149.0}, |
||||
] |
||||
|
||||
|
||||
func _add_island(info: Dictionary) -> void: |
||||
var center: Vector3 = Vector3(float(info.get("x", 0.0)), 0.0, float(info.get("z", 0.0))) |
||||
var radius: float = float(info.get("radius", 45.0)) |
||||
var height: float = float(info.get("height", 16.0)) |
||||
var seed: float = float(info.get("seed", 1.0)) |
||||
var mesh := ArrayMesh.new() |
||||
var vertices := PackedVector3Array() |
||||
var normals := PackedVector3Array() |
||||
var uvs := PackedVector2Array() |
||||
var indices := PackedInt32Array() |
||||
for z in range(ISLAND_GRID + 1): |
||||
for x in range(ISLAND_GRID + 1): |
||||
var fx := (float(x) / ISLAND_GRID - 0.5) * radius * 2.35 |
||||
var fz := (float(z) / ISLAND_GRID - 0.5) * radius * 2.35 |
||||
var d := Vector2(fx, fz).length() / radius |
||||
var core := pow(clampf(1.0 - d, 0.0, 1.0), 2.0) |
||||
var beach_ring := smoothstep(1.04, 0.72, d) |
||||
var ridge_a := sin((fx + seed) * 0.055) * cos((fz - seed) * 0.047) |
||||
var ridge_b := sin((fx * 0.12 - fz * 0.09) + seed * 0.31) |
||||
var ridges := (ridge_a * 0.30 + ridge_b * 0.14) * core |
||||
var detail := sin((fx - fz) * 0.21 + seed) * sin((fx + fz) * 0.13) * 0.07 * core |
||||
var y := (core + ridges + detail) * height |
||||
y = y * beach_ring - smoothstep(0.76, 1.05, d) * 1.6 |
||||
if d > 0.70: |
||||
y = minf(y, 1.3 + (1.0 - d) * 2.2) |
||||
vertices.append(center + Vector3(fx, y, fz)) |
||||
normals.append(Vector3.UP) |
||||
uvs.append(Vector2(float(x) / ISLAND_GRID, float(z) / ISLAND_GRID)) |
||||
for z in range(ISLAND_GRID): |
||||
for x in range(ISLAND_GRID): |
||||
var i := z * (ISLAND_GRID + 1) + x |
||||
indices.append_array([i, i + ISLAND_GRID + 1, i + 1, i + 1, i + ISLAND_GRID + 1, i + ISLAND_GRID + 2]) |
||||
var arrays := [] |
||||
arrays.resize(Mesh.ARRAY_MAX) |
||||
arrays[Mesh.ARRAY_VERTEX] = vertices |
||||
arrays[Mesh.ARRAY_NORMAL] = _derive_normals(vertices, indices) |
||||
arrays[Mesh.ARRAY_TEX_UV] = uvs |
||||
arrays[Mesh.ARRAY_INDEX] = indices |
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) |
||||
var island := MeshInstance3D.new() |
||||
island.mesh = mesh |
||||
island.material_override = island_material |
||||
island_root.add_child(island) |
||||
_add_palms(center, radius, height, int(seed)) |
||||
|
||||
|
||||
func _derive_normals(vertices: PackedVector3Array, indices: PackedInt32Array) -> PackedVector3Array: |
||||
var normals := PackedVector3Array() |
||||
normals.resize(vertices.size()) |
||||
for i in range(0, indices.size(), 3): |
||||
var a := indices[i] |
||||
var b := indices[i + 1] |
||||
var c := indices[i + 2] |
||||
var n := (vertices[b] - vertices[a]).cross(vertices[c] - vertices[a]).normalized() |
||||
normals[a] += n |
||||
normals[b] += n |
||||
normals[c] += n |
||||
for i in range(normals.size()): |
||||
normals[i] = normals[i].normalized() |
||||
return normals |
||||
|
||||
|
||||
func _add_palms(center: Vector3, radius: float, height: float, seed: int) -> void: |
||||
var trunk_mat := StandardMaterial3D.new() |
||||
trunk_mat.albedo_color = Color(0.36, 0.22, 0.11) |
||||
trunk_mat.roughness = 0.9 |
||||
var leaf_mat := StandardMaterial3D.new() |
||||
leaf_mat.albedo_color = Color(0.06, 0.38, 0.14) |
||||
leaf_mat.roughness = 0.8 |
||||
for i in range(8): |
||||
var a := float(seed * 37 + i * 53) |
||||
var r := radius * (0.24 + absf(sin(a * 1.71)) * 0.42) |
||||
var angle := a * 0.37 |
||||
var pos := center + Vector3(cos(angle) * r, height * 0.34, sin(angle) * r) |
||||
var trunk := MeshInstance3D.new() |
||||
trunk.mesh = CylinderMesh.new() |
||||
(trunk.mesh as CylinderMesh).top_radius = 0.22 |
||||
(trunk.mesh as CylinderMesh).bottom_radius = 0.34 |
||||
(trunk.mesh as CylinderMesh).height = 6.5 |
||||
trunk.position = pos + Vector3(0, 3.0, 0) |
||||
trunk.rotation.z = sin(a) * 0.18 |
||||
trunk.material_override = trunk_mat |
||||
island_root.add_child(trunk) |
||||
for k in range(7): |
||||
var leaf := MeshInstance3D.new() |
||||
var plane := PlaneMesh.new() |
||||
plane.size = Vector2(1.7, 6.4) |
||||
leaf.mesh = plane |
||||
leaf.position = pos + Vector3(0, 6.8, 0) |
||||
leaf.rotation = Vector3(deg_to_rad(68.0), angle + TAU * float(k) / 7.0, 0.0) |
||||
leaf.material_override = leaf_mat |
||||
island_root.add_child(leaf) |
||||
|
||||
|
||||
func _create_markers() -> void: |
||||
marker_root = Node3D.new() |
||||
marker_root.name = "Markers" |
||||
add_child(marker_root) |
||||
var markers: Array = _bridge_array("get_markers", [760.0]) |
||||
if markers.is_empty(): |
||||
markers = [ |
||||
{"x": -90.0, "z": -155.0, "type": 1.0, "size": 2.6}, |
||||
{"x": 285.0, "z": 70.0, "type": 2.0, "size": 4.5}, |
||||
{"x": -390.0, "z": 280.0, "type": 3.0, "size": 7.0}, |
||||
] |
||||
for marker in markers: |
||||
if marker is Dictionary: |
||||
_add_marker(marker) |
||||
|
||||
|
||||
func _add_marker(info: Dictionary) -> void: |
||||
var type: int = int(info.get("type", 0)) |
||||
var size: float = float(info.get("size", 2.0)) |
||||
var x: float = float(info.get("x", 0.0)) |
||||
var z: float = float(info.get("z", 0.0)) |
||||
var mat := StandardMaterial3D.new() |
||||
mat.albedo_color = Color(0.85, 0.12, 0.08) if type != 2 else Color(0.1, 0.25, 0.95) |
||||
mat.emission_enabled = type == 3 |
||||
mat.emission = Color(1.0, 0.75, 0.32) |
||||
mat.emission_energy_multiplier = 1.4 |
||||
var node := MeshInstance3D.new() |
||||
node.mesh = CylinderMesh.new() |
||||
(node.mesh as CylinderMesh).top_radius = size * 0.22 |
||||
(node.mesh as CylinderMesh).bottom_radius = size * 0.35 |
||||
(node.mesh as CylinderMesh).height = size * 2.4 |
||||
node.position = Vector3(x, size * 1.1, z) |
||||
node.material_override = mat |
||||
marker_root.add_child(node) |
||||
if type == 3: |
||||
var light := OmniLight3D.new() |
||||
light.light_color = Color(1.0, 0.62, 0.26) |
||||
light.light_energy = 1.8 |
||||
light.omni_range = 34.0 |
||||
node.add_child(light) |
||||
|
||||
|
||||
func _create_boat() -> void: |
||||
boat = Node3D.new() |
||||
boat.name = "Sailboat" |
||||
add_child(boat) |
||||
var hull_mat := StandardMaterial3D.new() |
||||
hull_mat.albedo_color = Color(0.34, 0.14, 0.055) |
||||
hull_mat.roughness = 0.72 |
||||
var trim_mat := StandardMaterial3D.new() |
||||
trim_mat.albedo_color = Color(0.72, 0.54, 0.30) |
||||
trim_mat.roughness = 0.62 |
||||
var hull := MeshInstance3D.new() |
||||
var hull_mesh := BoxMesh.new() |
||||
hull_mesh.size = Vector3(3.2, 0.9, 8.0) |
||||
hull.mesh = hull_mesh |
||||
hull.position.y = 0.48 |
||||
hull.scale = Vector3(1.0, 0.7, 1.0) |
||||
hull.material_override = hull_mat |
||||
boat.add_child(hull) |
||||
var mast := MeshInstance3D.new() |
||||
mast.mesh = CylinderMesh.new() |
||||
(mast.mesh as CylinderMesh).top_radius = 0.08 |
||||
(mast.mesh as CylinderMesh).bottom_radius = 0.12 |
||||
(mast.mesh as CylinderMesh).height = 8.0 |
||||
mast.position = Vector3(0.0, 4.1, -0.7) |
||||
mast.material_override = trim_mat |
||||
boat.add_child(mast) |
||||
_add_sail(Vector3(0.08, 4.8, -0.8), 3.2, 6.0, false) |
||||
_add_sail(Vector3(-0.08, 3.5, -0.35), 2.2, 4.0, true) |
||||
var lamp := OmniLight3D.new() |
||||
lamp.name = "WarmLantern" |
||||
lamp.position = Vector3(0.0, 1.35, -3.4) |
||||
lamp.light_color = Color(1.0, 0.58, 0.24) |
||||
lamp.light_energy = 1.2 |
||||
lamp.omni_range = 18.0 |
||||
boat.add_child(lamp) |
||||
|
||||
|
||||
func _add_sail(offset: Vector3, width: float, height: float, flip: bool) -> void: |
||||
var mesh := ArrayMesh.new() |
||||
var vertices := PackedVector3Array([ |
||||
Vector3(0, -height * 0.5, 0), |
||||
Vector3(width * ( -1.0 if flip else 1.0), -height * 0.35, 0), |
||||
Vector3(0, height * 0.5, 0), |
||||
]) |
||||
var arrays := [] |
||||
arrays.resize(Mesh.ARRAY_MAX) |
||||
arrays[Mesh.ARRAY_VERTEX] = vertices |
||||
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array([Vector3.BACK, Vector3.BACK, Vector3.BACK]) |
||||
arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2]) |
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) |
||||
var sail := MeshInstance3D.new() |
||||
sail.mesh = mesh |
||||
sail.position = offset |
||||
sail.material_override = sail_material |
||||
boat.add_child(sail) |
||||
|
||||
|
||||
func _update_boat(dt: float) -> void: |
||||
var input: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back") |
||||
var forward := Vector3(-sin(camera_yaw), 0.0, -cos(camera_yaw)) |
||||
var right := Vector3(cos(camera_yaw), 0.0, -sin(camera_yaw)) |
||||
var desired := (right * input.x + forward * -input.y) |
||||
if desired.length_squared() > 0.001: |
||||
desired = desired.normalized() |
||||
boat_yaw = lerp_angle(boat_yaw, atan2(desired.x, -desired.z), 1.0 - pow(0.05, dt)) |
||||
var wind := Vector3(-0.35, 0.0, -0.94).normalized() |
||||
var sail_power := clampf(0.55 + max(0.0, desired.dot(wind)) * 0.85, 0.35, 1.6) |
||||
var boost: float = 1.55 if Input.is_action_pressed("boost") else 1.0 |
||||
boat_velocity += desired * 13.0 * sail_power * boost * dt |
||||
boat_velocity *= pow(0.10, dt) |
||||
boat_position += boat_velocity * dt |
||||
var boat_forward := Vector2(-sin(boat_yaw), -cos(boat_yaw)) |
||||
ocean_material.set_shader_parameter("boat_pos", boat_position) |
||||
ocean_material.set_shader_parameter("boat_forward", boat_forward) |
||||
ocean_material.set_shader_parameter("boat_speed", boat_velocity.length()) |
||||
var bob := _water_height(boat_position.x, boat_position.z) |
||||
boat.position = Vector3(boat_position.x, bob + 0.52, boat_position.z) |
||||
boat.rotation = Vector3(sin(Time.get_ticks_msec() * 0.0017) * 0.035, boat_yaw, sin(Time.get_ticks_msec() * 0.0011) * 0.055) |
||||
|
||||
|
||||
func _water_height(x: float, z: float) -> float: |
||||
var t: float = Time.get_ticks_msec() * 0.001 |
||||
var p := Vector2(x, z) |
||||
return sin(p.dot(Vector2(0.92, 0.28).normalized()) * 0.035 + t * 0.54) * 0.72 + sin(p.dot(Vector2(0.38, 1.0).normalized()) * 0.070 + t * 0.86) * 0.38 + sin(p.dot(Vector2(-0.68, 0.55).normalized()) * 0.155 + t * 1.58) * 0.16 |
||||
|
||||
|
||||
func _update_weather(dt: float) -> void: |
||||
day_time = fmod(day_time + dt / 180.0, 1.0) |
||||
next_weather_change -= dt |
||||
if next_weather_change <= 0.0 and target_weather == weather: |
||||
target_weather = _choose_next_weather(weather) |
||||
weather_mix = 0.0 |
||||
next_weather_change = 20.0 + randf() * 22.0 |
||||
if target_weather != weather: |
||||
weather_mix = minf(1.0, weather_mix + dt / 9.0) |
||||
if weather_mix >= 1.0: |
||||
weather = target_weather |
||||
weather_mix = 0.0 |
||||
var base_rain := 1.0 if weather == WEATHER_RAIN else 0.0 |
||||
if target_weather == WEATHER_RAIN: |
||||
rain_amount = maxf(base_rain, weather_mix) |
||||
elif weather == WEATHER_RAIN: |
||||
rain_amount = 1.0 - weather_mix |
||||
else: |
||||
rain_amount = 0.0 |
||||
|
||||
|
||||
func _choose_next_weather(current: int) -> int: |
||||
var roll := randf() |
||||
if bridge != null and bridge.has_method("choose_next_weather"): |
||||
return int(bridge.call("choose_next_weather", current, roll)) |
||||
if current == WEATHER_SUNNY: |
||||
return WEATHER_CLOUDY if roll < 0.58 else WEATHER_RAIN |
||||
if current == WEATHER_CLOUDY: |
||||
return WEATHER_SUNNY if roll < 0.42 else WEATHER_RAIN |
||||
return WEATHER_CLOUDY if roll < 0.58 else WEATHER_SUNNY |
||||
|
||||
|
||||
func _update_environment(_dt: float) -> void: |
||||
var sun_angle := day_time * TAU - PI * 0.5 |
||||
var sun_dir := Vector3(cos(sun_angle) * -0.52, sin(sun_angle), -0.62).normalized() |
||||
sun.rotation = Basis.looking_at(-sun_dir, Vector3.UP).get_euler() |
||||
var daylight := smoothstep(-0.12, 0.28, sun_dir.y) |
||||
var storm := clampf((0.55 if target_weather == WEATHER_CLOUDY else 0.0) + rain_amount, 0.0, 1.0) |
||||
sun.light_energy = lerpf(0.04, 3.4, daylight) * (1.0 - storm * 0.58) |
||||
moon.light_energy = (1.0 - daylight) * 0.48 |
||||
ocean_material.set_shader_parameter("storm", storm) |
||||
ocean_material.set_shader_parameter("rain", rain_amount) |
||||
ocean_material.set_shader_parameter("sun_dir", sun_dir) |
||||
sky_material.set_shader_parameter("day_time", day_time) |
||||
sky_material.set_shader_parameter("storm", storm) |
||||
sky_material.set_shader_parameter("sun_dir", sun_dir) |
||||
for material in cloud_materials: |
||||
material.set_shader_parameter("storm", storm) |
||||
material.set_shader_parameter("coverage", lerpf(0.46, 0.28, rain_amount)) |
||||
rain.emitting = rain_amount > 0.05 |
||||
rain.amount_ratio = rain_amount |
||||
var env: Environment = world_environment.environment |
||||
env.fog_density = lerpf(0.004, 0.018, storm) |
||||
env.volumetric_fog_density = lerpf(0.012, 0.045, storm) |
||||
|
||||
|
||||
func _update_camera(dt: float) -> void: |
||||
var distance := lerpf(26.0, 34.0, clampf(boat_velocity.length() / 16.0, 0.0, 1.0)) |
||||
var camera_offset := Basis(Vector3.UP, camera_yaw) * Vector3(0.0, 8.8, distance) |
||||
var target_height := maxf(2.2, 5.0 + sin(camera_pitch) * 10.0) |
||||
var target := boat_position + Vector3(0.0, target_height, 0.0) |
||||
camera_rig.position = camera_rig.position.lerp(target, 1.0 - pow(0.01, dt)) |
||||
camera.position = camera.position.lerp(camera_offset, 1.0 - pow(0.02, dt)) |
||||
camera.look_at(target, Vector3.UP) |
||||
|
||||
|
||||
func _bridge_array(method: StringName, args: Array) -> Array: |
||||
if bridge == null or not bridge.has_method(method): |
||||
return [] |
||||
var value = bridge.callv(method, args) |
||||
return value if value is Array else [] |
||||
|
||||
|
||||
func _ensure_input_map() -> void: |
||||
_add_key_action("move_forward", [KEY_W, KEY_UP]) |
||||
_add_key_action("move_back", [KEY_S, KEY_DOWN]) |
||||
_add_key_action("move_left", [KEY_A, KEY_LEFT]) |
||||
_add_key_action("move_right", [KEY_D, KEY_RIGHT]) |
||||
_add_key_action("toggle_mouse", [KEY_ESCAPE]) |
||||
_add_key_action("boost", [KEY_SHIFT]) |
||||
|
||||
|
||||
func _add_key_action(action: StringName, keys: Array) -> void: |
||||
if not InputMap.has_action(action): |
||||
InputMap.add_action(action) |
||||
for key in keys: |
||||
var exists := false |
||||
for event in InputMap.action_get_events(action): |
||||
if event is InputEventKey and event.keycode == key: |
||||
exists = true |
||||
break |
||||
if exists: |
||||
continue |
||||
var input := InputEventKey.new() |
||||
input.keycode = key |
||||
input.physical_keycode = key |
||||
InputMap.action_add_event(action, input) |
||||
@ -0,0 +1,7 @@ |
||||
name: typephp_ocean |
||||
mode: lib |
||||
version: 0.1.0 |
||||
cxx-std: c++17 |
||||
sources: |
||||
- php-src |
||||
- cpp-src |
||||
@ -0,0 +1,10 @@ |
||||
[configuration] |
||||
entry_symbol = "typephp_ocean_bridge_init" |
||||
compatibility_minimum = "4.4" |
||||
reloadable = true |
||||
|
||||
[libraries] |
||||
windows.debug.x86_64 = "res://bin/typephp_ocean_godot_bridge.dll" |
||||
windows.release.x86_64 = "res://bin/typephp_ocean_godot_bridge.dll" |
||||
windows.template_debug.x86_64 = "res://bin/typephp_ocean_godot_bridge.dll" |
||||
windows.template_release.x86_64 = "res://bin/typephp_ocean_godot_bridge.dll" |
||||
Loading…
Reference in new issue