diff --git a/BINDINGS.md b/BINDINGS.md index 986c27abc..719a902a0 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -18,6 +18,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [cl-raylib](https://github.com/longlene/cl-raylib) | 4.0 | [Common Lisp](https://common-lisp.net) | MIT | | [claylib/wrap](https://github.com/defun-games/claylib) | 4.5 | [Common Lisp](https://common-lisp.net) | Zlib | | [claw-raylib](https://github.com/bohonghuang/claw-raylib) | **auto** | [Common Lisp](https://common-lisp.net) | Apache-2.0 | +| [raylib](https://github.com/fosskers/raylib) | 5.5 | [Common Lisp](https://common-lisp.net) | MPL-2.0 | | [chez-raylib](https://github.com/Yunoinsky/chez-raylib) | **auto** | [Chez Scheme](https://cisco.github.io/ChezScheme) | GPLv3 | | [CLIPSraylib](https://github.com/mrryanjohnston/CLIPSraylib) | **auto** | [CLIPS](https://www.clipsrules.net/) | MIT | | [raylib-cr](https://github.com/sol-vin/raylib-cr) | 4.6-dev (5e1a81) | [Crystal](https://crystal-lang.org) | Apache-2.0 | @@ -50,7 +51,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-luajit-generated](https://github.com/james2doyle/raylib-luajit-generated) | 5.5 | [Lua](http://www.lua.org) | MIT | | [raylib-matte](https://github.com/jcorks/raylib-matte) | 4.6-dev | [Matte](https://github.com/jcorks/matte) | **???** | | [Raylib.nelua](https://github.com/AuzFox/Raylib.nelua) | **5.5** | [nelua](https://nelua.io) | Zlib | -| [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib | +| [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib | | [naylib](https://github.com/planetis-m/naylib) | **5.6-dev** | [Nim](https://nim-lang.org) | MIT | | [node-raylib](https://github.com/RobLoach/node-raylib) | 4.5 | [Node.js](https://nodejs.org/en) | Zlib | | [raylib-odin](https://github.com/odin-lang/Odin/tree/master/vendor/raylib) | **5.5** | [Odin](https://odin-lang.org) | BSD-3Clause | diff --git a/build.zig b/build.zig index e445bf576..14e9af21e 100644 --- a/build.zig +++ b/build.zig @@ -4,6 +4,9 @@ const builtin = @import("builtin"); /// Minimum supported version of Zig const min_ver = "0.13.0"; +const emccOutputDir = "zig-out" ++ std.fs.path.sep_str ++ "htmlout" ++ std.fs.path.sep_str; +const emccOutputFile = "index.html"; + comptime { const order = std.SemanticVersion.order; const parse = std.SemanticVersion.parse; @@ -45,6 +48,26 @@ fn emSdkSetupStep(b: *std.Build, emsdk: *std.Build.Dependency) !?*std.Build.Step } } +// Adapted from Not-Nik/raylib-zig +fn emscriptenRunStep(b: *std.Build, emsdk: *std.Build.Dependency, examplePath: []const u8) !*std.Build.Step.Run { + const dot_emsc_path = emsdk.path("upstream/emscripten/cache/sysroot/include").getPath(b); + // If compiling on windows , use emrun.bat. + const emrunExe = switch (builtin.os.tag) { + .windows => "emrun.bat", + else => "emrun", + }; + var emrun_run_arg = try b.allocator.alloc(u8, dot_emsc_path.len + emrunExe.len + 1); + defer b.allocator.free(emrun_run_arg); + + if (b.sysroot == null) { + emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}", .{emrunExe}); + } else { + emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ dot_emsc_path, emrunExe }); + } + const run_cmd = b.addSystemCommand(&.{ emrun_run_arg, examplePath }); + return run_cmd; +} + /// A list of all flags from `src/config.h` that one may override const config_h_flags = outer: { // Set this value higher if compile errors happen as `src/config.h` gets larger @@ -339,7 +362,6 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. setDesktopPlatform(raylib, options.platform); }, .emscripten => { - // Include emscripten for cross compilation if (b.lazyDependency("emsdk", .{})) |dep| { if (try emSdkSetupStep(b, dep)) |emSdkStep| { raylib.step.dependOn(&emSdkStep.step); @@ -511,12 +533,9 @@ fn addExamples( optimize: std.builtin.OptimizeMode, raylib: *std.Build.Step.Compile, ) !*std.Build.Step { - if (target.result.os.tag == .emscripten) { - return &b.addFail("Emscripten building via Zig unsupported").step; - } - const all = b.step(module, "All " ++ module ++ " examples"); const module_subpath = b.pathJoin(&.{ "examples", module }); + const module_resources = b.pathJoin(&.{ module_subpath, "resources@resources" }); var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true }); defer if (comptime builtin.zig_version.minor >= 12) dir.close(); @@ -530,71 +549,154 @@ fn addExamples( // zig's mingw headers do not include pthread.h if (std.mem.eql(u8, "core_loading_thread", name) and target.result.os.tag == .windows) continue; - const exe = b.addExecutable(.{ - .name = name, - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ .file = b.path(path), .flags = &.{} }); - exe.linkLibC(); + if (target.result.os.tag == .emscripten) { + const exe_lib = b.addStaticLibrary(.{ + .name = name, + .target = target, + .optimize = optimize, + }); + exe_lib.addCSourceFile(.{ + .file = b.path(path), + .flags = &.{}, + }); + exe_lib.linkLibC(); - // special examples that test using these external dependencies directly - // alongside raylib - if (std.mem.eql(u8, name, "rlgl_standalone")) { - exe.addIncludePath(b.path("src")); - exe.addIncludePath(b.path("src/external/glfw/include")); - if (!hasCSource(raylib.root_module, "rglfw.c")) { - exe.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} }); + if (std.mem.eql(u8, name, "rlgl_standalone")) { + //TODO: Make rlgl_standalone example work + continue; } + if (std.mem.eql(u8, name, "raylib_opengl_interop")) { + //TODO: Make raylib_opengl_interop example work + continue; + } + + exe_lib.linkLibrary(raylib); + + // Include emscripten for cross compilation + if (b.lazyDependency("emsdk", .{})) |emsdk_dep| { + if (try emSdkSetupStep(b, emsdk_dep)) |emSdkStep| { + exe_lib.step.dependOn(&emSdkStep.step); + } + + exe_lib.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include")); + + // Create the output directory because emcc can't do it. + const emccOutputDirExample = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str }); + const mkdir_command = switch (builtin.os.tag) { + .windows => b.addSystemCommand(&.{ "cmd.exe", "/c", "if", "not", "exist", emccOutputDirExample, "mkdir", emccOutputDirExample }), + else => b.addSystemCommand(&.{ "mkdir", "-p", emccOutputDirExample }), + }; + + const emcc_exe = switch (builtin.os.tag) { + .windows => "emcc.bat", + else => "emcc", + }; + + const emcc_exe_path = b.pathJoin(&.{ emsdk_dep.path("upstream/emscripten").getPath(b), emcc_exe }); + const emcc_command = b.addSystemCommand(&[_][]const u8{emcc_exe_path}); + emcc_command.step.dependOn(&mkdir_command.step); + const emccOutputDirExampleWithFile = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str, emccOutputFile }); + emcc_command.addArgs(&[_][]const u8{ + "-o", + emccOutputDirExampleWithFile, + "-sFULL-ES3=1", + "-sUSE_GLFW=3", + "-sSTACK_OVERFLOW_CHECK=1", + "-sEXPORTED_RUNTIME_METHODS=['requestFullscreen']", + "-sASYNCIFY", + "-O0", + "--emrun", + "--preload-file", + module_resources, + "--shell-file", + b.path("src/shell.html").getPath(b), + }); + + const link_items: []const *std.Build.Step.Compile = &.{ + raylib, + exe_lib, + }; + for (link_items) |item| { + emcc_command.addFileArg(item.getEmittedBin()); + emcc_command.step.dependOn(&item.step); + } + + const run_step = try emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile); + run_step.step.dependOn(&emcc_command.step); + run_step.addArg("--no_browser"); + const run_option = b.step(name, name); + + run_option.dependOn(&run_step.step); + + all.dependOn(&emcc_command.step); + } + } else { + const exe = b.addExecutable(.{ + .name = name, + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ .file = b.path(path), .flags = &.{} }); + exe.linkLibC(); + + // special examples that test using these external dependencies directly + // alongside raylib + if (std.mem.eql(u8, name, "rlgl_standalone")) { + exe.addIncludePath(b.path("src")); + exe.addIncludePath(b.path("src/external/glfw/include")); + if (!hasCSource(raylib.root_module, "rglfw.c")) { + exe.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} }); + } + } + if (std.mem.eql(u8, name, "raylib_opengl_interop")) { + exe.addIncludePath(b.path("src/external")); + } + + exe.linkLibrary(raylib); + + switch (target.result.os.tag) { + .windows => { + exe.linkSystemLibrary("winmm"); + exe.linkSystemLibrary("gdi32"); + exe.linkSystemLibrary("opengl32"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + .linux => { + exe.linkSystemLibrary("GL"); + exe.linkSystemLibrary("rt"); + exe.linkSystemLibrary("dl"); + exe.linkSystemLibrary("m"); + exe.linkSystemLibrary("X11"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + .macos => { + exe.linkFramework("Foundation"); + exe.linkFramework("Cocoa"); + exe.linkFramework("OpenGL"); + exe.linkFramework("CoreAudio"); + exe.linkFramework("CoreVideo"); + exe.linkFramework("IOKit"); + + exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); + }, + else => { + @panic("Unsupported OS"); + }, + } + + const install_cmd = b.addInstallArtifact(exe, .{}); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.cwd = b.path(module_subpath); + run_cmd.step.dependOn(&install_cmd.step); + + const run_step = b.step(name, name); + run_step.dependOn(&run_cmd.step); + + all.dependOn(&install_cmd.step); } - if (std.mem.eql(u8, name, "raylib_opengl_interop")) { - exe.addIncludePath(b.path("src/external")); - } - - exe.linkLibrary(raylib); - - switch (target.result.os.tag) { - .windows => { - exe.linkSystemLibrary("winmm"); - exe.linkSystemLibrary("gdi32"); - exe.linkSystemLibrary("opengl32"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - .linux => { - exe.linkSystemLibrary("GL"); - exe.linkSystemLibrary("rt"); - exe.linkSystemLibrary("dl"); - exe.linkSystemLibrary("m"); - exe.linkSystemLibrary("X11"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - .macos => { - exe.linkFramework("Foundation"); - exe.linkFramework("Cocoa"); - exe.linkFramework("OpenGL"); - exe.linkFramework("CoreAudio"); - exe.linkFramework("CoreVideo"); - exe.linkFramework("IOKit"); - - exe.root_module.addCMacro("PLATFORM_DESKTOP", ""); - }, - else => { - @panic("Unsupported OS"); - }, - } - - const install_cmd = b.addInstallArtifact(exe, .{}); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.cwd = b.path(module_subpath); - run_cmd.step.dependOn(&install_cmd.step); - - const run_step = b.step(name, name); - run_step.dependOn(&run_cmd.step); - - all.dependOn(&install_cmd.step); } return all; } diff --git a/examples/shaders/resources/shaders/glsl100/pbr.vs b/examples/shaders/resources/shaders/glsl100/pbr.vs index a55c0ea4b..baf003842 100644 --- a/examples/shaders/resources/shaders/glsl100/pbr.vs +++ b/examples/shaders/resources/shaders/glsl100/pbr.vs @@ -4,7 +4,7 @@ attribute vec3 vertexPosition; attribute vec2 vertexTexCoord; attribute vec3 vertexNormal; -attribute vec3 vertexTangent; +attribute vec4 vertexTangent; attribute vec4 vertexColor; // Input uniform values @@ -52,7 +52,7 @@ mat3 transpose(mat3 m) void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); diff --git a/examples/shaders/resources/shaders/glsl120/pbr.vs b/examples/shaders/resources/shaders/glsl120/pbr.vs index d3cc66488..e9750a60a 100644 --- a/examples/shaders/resources/shaders/glsl120/pbr.vs +++ b/examples/shaders/resources/shaders/glsl120/pbr.vs @@ -4,7 +4,7 @@ attribute vec3 vertexPosition; attribute vec2 vertexTexCoord; attribute vec3 vertexNormal; -attribute vec3 vertexTangent; +attribute vec4 vertexTangent; attribute vec4 vertexColor; // Input uniform values @@ -52,7 +52,7 @@ mat3 transpose(mat3 m) void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -62,7 +62,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); @@ -71,4 +71,4 @@ void main() // Calculate final vertex position gl_Position = mvp*vec4(vertexPosition, 1.0); -} \ No newline at end of file +} diff --git a/examples/shaders/resources/shaders/glsl330/pbr.vs b/examples/shaders/resources/shaders/glsl330/pbr.vs index 6f262313c..8aabb6baf 100644 --- a/examples/shaders/resources/shaders/glsl330/pbr.vs +++ b/examples/shaders/resources/shaders/glsl330/pbr.vs @@ -4,7 +4,7 @@ in vec3 vertexPosition; in vec2 vertexTexCoord; in vec3 vertexNormal; -in vec3 vertexTangent; +in vec4 vertexTangent; in vec4 vertexColor; // Input uniform values @@ -26,7 +26,7 @@ const float normalOffset = 0.1; void main() { // Compute binormal from vertex normal and tangent - vec3 vertexBinormal = cross(vertexNormal, vertexTangent); + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; // Compute fragment normal based on normal transformations mat3 normalMatrix = transpose(inverse(mat3(matModel))); @@ -36,7 +36,7 @@ void main() fragTexCoord = vertexTexCoord*2.0; fragNormal = normalize(normalMatrix*vertexNormal); - vec3 fragTangent = normalize(normalMatrix*vertexTangent); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); fragBinormal = cross(fragNormal, fragTangent); @@ -45,4 +45,4 @@ void main() // Calculate final vertex position gl_Position = mvp*vec4(vertexPosition, 1.0); -} \ No newline at end of file +} diff --git a/src/rmodels.c b/src/rmodels.c index 84daabc15..ac6f4c594 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5577,7 +5577,7 @@ static Model LoadGLTF(const char *fileName) } else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName); } - else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec3, float + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec4, float, w is tangent basis sign { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; @@ -5593,10 +5593,10 @@ static Model LoadGLTF(const char *fileName) float *tangents = model.meshes[meshIndex].tangents; for (unsigned int k = 0; k < attribute->count; k++) { - Vector3 tt = Vector3Transform((Vector3){ tangents[3*k], tangents[3*k+1], tangents[3*k+2] }, worldMatrix); - tangents[3*k] = tt.x; - tangents[3*k+1] = tt.y; - tangents[3*k+2] = tt.z; + Vector3 tt = Vector3Transform((Vector3){ tangents[4*k], tangents[4*k+1], tangents[4*k+2] }, worldMatrix); + tangents[4*k] = tt.x; + tangents[4*k+1] = tt.y; + tangents[4*k+2] = tt.z; } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName);