From 3c0d4bcab685d1eef1e60b0f80885fb128807d19 Mon Sep 17 00:00:00 2001 From: Per Hultqvist Date: Thu, 17 Oct 2024 14:58:47 +0200 Subject: [PATCH] New example : Textures/draw_tiled --- examples/textures/draw_tiled/main.go | 302 ++++++++++++++++++++++ examples/textures/draw_tiled/patterns.png | Bin 0 -> 7387 bytes 2 files changed, 302 insertions(+) create mode 100644 examples/textures/draw_tiled/main.go create mode 100644 examples/textures/draw_tiled/patterns.png diff --git a/examples/textures/draw_tiled/main.go b/examples/textures/draw_tiled/main.go new file mode 100644 index 0000000..9d38c18 --- /dev/null +++ b/examples/textures/draw_tiled/main.go @@ -0,0 +1,302 @@ +/******************************************************************************************* + * + * raylib [textures] example - Draw part of the texture tiled + * + * Example originally created with raylib 3.0, last time updated with raylib 4.2 + * + * Example contributed by Vlad Adrian (@demizdor) and reviewed by Ramon Santamaria (@raysan5) + * + * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, + * BSD-like license that allows static linking with closed source software + * + * Copyright (c) 2020-2024 Vlad Adrian (@demizdor) and Ramon Santamaria (@raysan5) + * + ********************************************************************************************/ +package main + +import ( + "fmt" + + rl "github.com/gen2brain/raylib-go/raylib" +) + +const ( + screenWidth = 800 + screenHeight = 450 + optWidth = 220 // Max width for the options container + marginSize = 8 // Size for the margins + colorSize = 16 // Size of the color select buttons +) + +func main() { + rl.SetConfigFlags(rl.FlagWindowResizable) // Make the window resizable + rl.InitWindow(screenWidth, screenHeight, "raylib [textures] example - Draw part of a texture tiled") + + // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) + texPattern := rl.LoadTexture("patterns.png") + rl.SetTextureFilter(texPattern, rl.TextureFilterNearestMipLinear) // Makes the texture smoother when upscaled + + // Coordinates for all patterns inside the texture + recPattern := []rl.Rectangle{ + {3, 3, 66, 66}, + {75, 3, 100, 100}, + {3, 75, 66, 66}, + {7, 156, 50, 50}, + {85, 106, 90, 45}, + {75, 154, 100, 60}, + } + + // Setup colors + colors := []rl.Color{rl.Black, rl.Maroon, rl.Orange, rl.Blue, rl.Purple, + rl.Beige, rl.Lime, rl.Red, rl.DarkGray, rl.SkyBlue} + var maxColors = len(colors) + colorRec := make([]rl.Rectangle, maxColors) + + // Calculate rectangle for each color + var x, y float32 + for i := 0; i < maxColors; i++ { + colorRec[i].X = 2.0 + marginSize + x + colorRec[i].Y = 22.0 + 256.0 + marginSize + y + colorRec[i].Width = colorSize * 2.0 + colorRec[i].Height = colorSize + + if i == (maxColors/2 - 1) { + x = 0 + y += colorSize + marginSize + } else { + x += colorSize*2 + marginSize + } + } + + activePattern := 0 + activeCol := 0 + scale := float32(1.0) + rotation := float32(0.0) + + rl.SetTargetFPS(60) + + // Main game loop + for !rl.WindowShouldClose() { // Detect window close button or ESC key + // Handle mouse + if rl.IsMouseButtonPressed(rl.MouseButtonLeft) { + mouse := rl.GetMousePosition() + + // Check which pattern was clicked and set it as the active pattern + for i := 0; i < len(recPattern); i++ { + r := rl.Rectangle{ + X: 2 + marginSize + recPattern[i].X, + Y: 40 + marginSize + recPattern[i].Y, + Width: recPattern[i].Width, + Height: recPattern[i].Height, + } + if rl.CheckCollisionPointRec(mouse, r) { + activePattern = i + break + } + } + + // Check to see which color was clicked and set it as the active color + for i := 0; i < maxColors; i++ { + if rl.CheckCollisionPointRec(mouse, colorRec[i]) { + activeCol = i + break + } + } + } + + // Handle keys + + // Change scale + if rl.IsKeyPressed(rl.KeyUp) { + scale += 0.25 + } + if rl.IsKeyPressed(rl.KeyDown) { + scale -= 0.25 + } + scale = clamp(scale, 0.25, 10) + + // Change rotation + if rl.IsKeyPressed(rl.KeyLeft) { + rotation -= 25.0 + } + if rl.IsKeyPressed(rl.KeyRight) { + rotation += 25.0 + } + + // Reset + if rl.IsKeyPressed(rl.KeySpace) { + rotation = 0.0 + scale = 1.0 + } + + // Draw + rl.BeginDrawing() + rl.ClearBackground(rl.RayWhite) + + // Draw the tiled area + src := rl.Rectangle{ + X: optWidth + marginSize, + Y: marginSize, + Width: float32(rl.GetScreenWidth()) - optWidth - 2.0*marginSize, + Height: float32(rl.GetScreenHeight()) - 2.0*marginSize, + } + DrawTextureTiled(texPattern, recPattern[activePattern], src, rl.Vector2{}, rotation, scale, colors[activeCol]) + + // Draw options + rl.DrawRectangle(marginSize, marginSize, optWidth-marginSize, int32(rl.GetScreenHeight())-2*marginSize, + rl.ColorAlpha(rl.LightGray, 0.5)) + + rl.DrawText("Select Pattern", 2+marginSize, 30+marginSize, 10, rl.Black) + rl.DrawTexture(texPattern, 2+marginSize, 40+marginSize, rl.Black) + rl.DrawRectangle(int32(2+marginSize+recPattern[activePattern].X), + int32(40+marginSize+recPattern[activePattern].Y), + int32(recPattern[activePattern].Width), + int32(recPattern[activePattern].Height), rl.ColorAlpha(rl.DarkBlue, 0.3)) + + rl.DrawText("Select Color", 2+marginSize, 10+256+marginSize, 10, rl.Black) + for i := 0; i < maxColors; i++ { + rl.DrawRectangleRec(colorRec[i], colors[i]) + if activeCol == i { + rl.DrawRectangleLinesEx(colorRec[i], 3, rl.ColorAlpha(rl.White, 0.5)) + } + } + + rl.DrawText("Scale (UP/DOWN to change)", 2+marginSize, 80+256+marginSize, 10, rl.Black) + rl.DrawText(fmt.Sprintf("%.2fx", scale), 2+marginSize, 92+256+marginSize, 20, rl.Black) + + rl.DrawText("Rotation (LEFT/RIGHT to change)", 2+marginSize, 122+256+marginSize, 10, rl.Black) + rl.DrawText(fmt.Sprintf("%.0f degrees", rotation), 2+marginSize, 134+256+marginSize, 20, rl.Black) + + rl.DrawText("Press [SPACE] to reset", 2+marginSize, 164+256+marginSize, 10, rl.DarkBlue) + + // Draw FPS + rl.DrawText(fmt.Sprintf("%d FPS", rl.GetFPS()), 2+marginSize, 2+marginSize, 20, rl.Black) + rl.EndDrawing() + } + + // De-Initialization + rl.UnloadTexture(texPattern) // Unload texture + + rl.CloseWindow() // Close window and OpenGL context +} + +// DrawTextureTiled draws a part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +func DrawTextureTiled(texture rl.Texture2D, source, dest rl.Rectangle, origin rl.Vector2, rotation, scale float32, + tint rl.Color) { + + if (texture.ID <= 0) || (scale <= 0.0) { // Want see an infinite loop?!...just delete this line! + return + } + if (source.Width == 0) || (source.Height == 0) { + return + } + + tileWidth := source.Width * scale + tileHeight := source.Height * scale + if (dest.Width < tileWidth) && (dest.Height < tileHeight) { + // Can fit only one tile + src := rl.Rectangle{ + X: source.X, + Y: source.Y, + Width: dest.Width / tileWidth * source.Width, + Height: dest.Height / tileHeight * source.Height, + } + dst := rl.Rectangle{X: dest.X, Y: dest.Y, Width: dest.Width, Height: dest.Height} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } else if dest.Width <= tileWidth { + // Tiled vertically (one column) + var dy float32 + for ; dy+tileHeight < dest.Height; dy += tileHeight { + src := rl.Rectangle{ + X: source.X, + Y: source.Y, + Width: dest.Width / tileWidth * source.Width, + Height: source.Height, + } + dst := rl.Rectangle{X: dest.X, Y: dest.Y + dy, Width: dest.Width, Height: tileHeight} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + + // Fit last tile + if dy < dest.Height { + src := rl.Rectangle{X: source.X, Y: source.Y, + Width: (dest.Width / tileWidth) * source.Width, + Height: ((dest.Height - dy) / tileHeight) * source.Height, + } + dst := rl.Rectangle{X: dest.X, Y: dest.Y + dy, Width: dest.Width, Height: dest.Height - dy} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + } else if dest.Height <= tileHeight { + // Tiled horizontally (one row) + var dx float32 + for ; dx+tileWidth < dest.Width; dx += tileWidth { + src := rl.Rectangle{ + X: source.X, Y: source.Y, Width: source.Width, + Height: (dest.Height / tileHeight) * source.Height, + } + dst := rl.Rectangle{X: dest.X + dx, Y: dest.Y, Width: tileWidth, Height: dest.Height} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + + // Fit last tile + if dx < dest.Width { + src := rl.Rectangle{ + X: source.X, Y: source.Y, Width: ((dest.Width - dx) / tileWidth) * source.Width, + Height: (dest.Height / tileHeight) * source.Height, + } + dst := rl.Rectangle{X: dest.X + dx, Y: dest.Y, Width: dest.Width - dx, Height: dest.Height} + rl.DrawTexturePro(texture, src, + dst, origin, rotation, tint) + } + } else { + // Tiled both horizontally and vertically (rows and columns) + var dx float32 + for ; dx+tileWidth < dest.Width; dx += tileWidth { + var dy float32 + for ; dy+tileHeight < dest.Height; dy += tileHeight { + dst := rl.Rectangle{X: dest.X + dx, Y: dest.Y + dy, Width: tileWidth, Height: tileHeight} + rl.DrawTexturePro(texture, source, dst, origin, rotation, tint) + } + + if dy < dest.Height { + src := rl.Rectangle{ + X: source.X, Y: source.Y, + Width: source.Width, Height: ((dest.Height - dy) / tileHeight) * source.Height, + } + dst := rl.Rectangle{ + X: dest.X + dx, Y: dest.Y + dy, + Width: tileWidth, Height: dest.Height - dy, + } + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + } + + // Fit last column of tiles + if dx < dest.Width { + var dy float32 + for ; dy+tileHeight < dest.Height; dy += tileHeight { + src := rl.Rectangle{ + X: source.X, Y: source.Y, + Width: ((dest.Width - dx) / tileWidth) * source.Width, Height: source.Height, + } + dst := rl.Rectangle{X: dest.X + dx, Y: dest.Y + dy, Width: dest.Width - dx, Height: tileHeight} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + + // Draw final tile in the bottom right corner + if dy < dest.Height { + src := rl.Rectangle{ + X: source.X, Y: source.Y, + Width: ((dest.Width - dx) / tileWidth) * source.Width, + Height: ((dest.Height - dy) / tileHeight) * source.Height, + } + dst := rl.Rectangle{X: dest.X + dx, Y: dest.Y + dy, Width: dest.Width - dx, Height: dest.Height - dy} + rl.DrawTexturePro(texture, src, dst, origin, rotation, tint) + } + } + } +} + +func clamp(value, minValue, maxValue float32) float32 { + return min(maxValue, max(value, minValue)) +} diff --git a/examples/textures/draw_tiled/patterns.png b/examples/textures/draw_tiled/patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..58b3c372c7dbfaf53bca771a9dc32e83d37dbd0d GIT binary patch literal 7387 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