Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help: Can't Translate "core_2d_camera_mouse_zoom" Example to Go #206

Closed
FloatingSunfish opened this issue Oct 17, 2022 · 9 comments
Closed

Comments

@FloatingSunfish
Copy link

I'm trying to follow the 2D Camera Mouse Zoom example.

I've managed to translate most of it to Go, but I can't find equivalent functions in raylib-go for the following section:

// Draw the 3d grid, rotated 90 degrees and centered around 0,0 
// just so we have something in the XY plane
rlPushMatrix();
    rlTranslatef(0, 25*50, 0);
    rlRotatef(90, 1, 0, 0);
    DrawGrid(100, 50);
rlPopMatrix();

Here's my broken code for the section:

// Not sure what to do with `matrix` here.
matrix := rl.MatrixTranslate(0, 25*50, 0)
matrix = rl.MatrixRotate(rl.Vector3{X: 90, Y: 1, Z: 0}, 0)
rl.DrawGrid(100, 50)
_ = matrix

How do I translate the code from the example to Go?

@gen2brain
Copy link
Owner

See #171 and #186.

@FloatingSunfish
Copy link
Author

FloatingSunfish commented Oct 17, 2022

@gen2brain
Ah, this could be a problem for me then. 😓

You see, I planned to follow all the examples on the official raylib site in raylib-go so I can get a hang of most of raylib's features before working on actual projects.

I chose raylib-go because Go is my favorite language and I really like how it makes code clearer and simpler.

It's a bit sad that the Go bindings aren't complete in the sense that you can't follow all the tutorials, although I understand that maintaining this library for so many years has already been quite a feat, and I salute you for it.
Especially when it comes to helping out beginners to the library like me. 🙂

I wonder if the bindings for other languages are more complete, or if none of them are, and my only option is to learn C and use the actual raylib library directly if I want to be able to follow all the tutorials on the site.

I suppose it is a bit strange for the tutorials to use features that aren't in the cheatsheet, which I assume contains everything declared in raylib.h (though I haven't really compared the two that closely).

In any case, it's a real shame since Go is my favorite language and I was really hoping it would be smooth sailing, at least while following the tutorials.

I assume adding support for all raylib functions used in all the official tutorials won't be coming anytime soon?

@JupiterRider
Copy link
Contributor

JupiterRider commented Oct 17, 2022

@FloatingSunfish
Good news, cgo got you covered!

Raylib is very simple. It uses structs and "static" functions only. So writing this example in golang is simple and wouldn't end up in a total mess:

package main

// #include <raylib.h>
// #include <raymath.h>
// #include <rlgl.h>
// #include <stdlib.h>
import "C"
import (
	"runtime"
	"unsafe"
)

const (
	screenWidth  = 800
	screenHeight = 450
)

func init() {
	runtime.LockOSThread()
}

func main() {
	title := C.CString("raylib [core] example - 2d camera mouse zoom")
	// a CString is not managed by the garbage collector, so dispose it when done
	defer C.free(unsafe.Pointer(title))
	C.InitWindow(screenWidth, screenHeight, title)
	defer C.CloseWindow()

	var camera C.Camera2D
	camera.zoom = 1

	C.SetTargetFPS(60)

	text := C.CString("Mouse right button drag to move, mouse wheel to zoom")
	defer C.free(unsafe.Pointer(text))

	for !C.WindowShouldClose() {
		if C.IsMouseButtonDown(C.MOUSE_BUTTON_RIGHT) {
			delta := C.GetMouseDelta()
			delta = C.Vector2Scale(delta, -1/camera.zoom)
			camera.target = C.Vector2Add(camera.target, delta)
		}

		wheel := C.GetMouseWheelMove()
		if wheel != 0 {
			mouseWorldPos := C.GetScreenToWorld2D(C.GetMousePosition(), camera)
			camera.offset = C.GetMousePosition()
			camera.target = mouseWorldPos

			const zoomIncrement C.float = .125
			camera.zoom += wheel * zoomIncrement
			if camera.zoom < zoomIncrement {
				camera.zoom = zoomIncrement
			}
		}

		C.BeginDrawing()
		C.ClearBackground(C.BLACK)
		C.BeginMode2D(camera)

		C.rlPushMatrix()
		C.rlTranslatef(0, 25*50, 0)
		C.rlRotatef(90, 1, 0, 0)
		C.DrawGrid(100, 50)
		C.rlPopMatrix()

		C.DrawCircle(100, 100, 50, C.YELLOW)

		C.EndMode2D()
		C.DrawText(text, 10, 10, 20, C.WHITE)
		C.EndDrawing()
	}
}

@JupiterRider
Copy link
Contributor

Oh yeah, and here is the example with using raylib-go. You just need to import "C" and add these two header files.

package main

// #include <raylib.h>
// #include <rlgl.h>
import "C"
import (
	rl "github.com/gen2brain/raylib-go/raylib"
)

const (
	screenWidth  = 800
	screenHeight = 450
)

func main() {
	rl.InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera mouse zoom")
	defer rl.CloseWindow()

	camera := rl.Camera2D{Zoom: 1}

	rl.SetTargetFPS(60)

	for !rl.WindowShouldClose() {
		if rl.IsMouseButtonDown(rl.MouseRightButton) {
			delta := rl.GetMouseDelta()
			delta = rl.Vector2Scale(delta, -1/camera.Zoom)
			camera.Target = rl.Vector2Add(camera.Target, delta)
		}

		wheel := rl.GetMouseWheelMove()
		if wheel != 0 {
			mouseWorldPos := rl.GetScreenToWorld2D(rl.GetMousePosition(), camera)
			camera.Offset = rl.GetMousePosition()
			camera.Target = mouseWorldPos

			const zoomIncrement float32 = .125
			camera.Zoom += wheel * zoomIncrement
			if camera.Zoom < zoomIncrement {
				camera.Zoom = zoomIncrement
			}
		}

		rl.BeginDrawing()
		rl.ClearBackground(rl.Black)
		rl.BeginMode2D(camera)

		C.rlPushMatrix()
		C.rlTranslatef(0, 25*50, 0)
		C.rlRotatef(90, 1, 0, 0)
		rl.DrawGrid(100, 50)
		C.rlPopMatrix()

		rl.DrawCircle(100, 100, 50, rl.Yellow)

		rl.EndMode2D()
		rl.DrawText("Mouse right button drag to move, mouse wheel to zoom", 10, 10, 20, rl.White)
		rl.EndDrawing()
	}
}

I think this would be the best solution in the meanwhile.

@gen2brain
Copy link
Owner

@FloatingSunfish also, note that the user here #186 did some work in Technerder@f565649, but seems it is not finished. If you can help with that I am willing to merge the changes.

My guess is, some bindings probably have support for rlgl.h but usually bindings cover only the public Raylib interface. In C, it is easy to use whatever, but as @JupiterRider showed, it can be done.

@FloatingSunfish
Copy link
Author

FloatingSunfish commented Oct 18, 2022

@JupiterRider @gen2brain
Thank you both so much for you help! 😊
Looks like my raylib-go journey won't end here after all!

In case anyone finds this Issue in the future, here's my current project structure:

/myproject
    /cgo
        raylib.h
        rlgl.h
    go.mod
    go.sum
    main.go

And here's my code for main.go:

package main

// We're using `cgo` to use `raylib` functions that `raylib-go` doesn't have bindings for.
// Note that to do so, we include the C files we use somewhere in our project.
// I put them inside the `cgo` directory, but you can put them anywhere you want.
//
// NOTE: You will sometimes get false-positive C errors.
//       Simply restart VS Code and they should go away.
//
// IMPORTANT:
// C `include` comments are VERY sensitive.
// They must be the only comments attached to the C import statement,
// and there must NOT be a newline between them.

// #include <cgo/raylib.h>
// #include <cgo/rlgl.h>
import "C"

import (
	rl "github.com/gen2brain/raylib-go/raylib"
)

const (
	screen_width  int32 = 800
	screen_height int32 = 450
	fps           int32 = 60

	zoom_increment float32 = 0.125
)

var (
	camera rl.Camera2D
)

func init() {
	rl.InitWindow(screen_width, screen_height, "raylib [core] example - 2d camera mouse zoom")
	rl.SetTargetFPS(fps)

	camera = rl.Camera2D{Zoom: 1}
}

func main() {
	for !rl.WindowShouldClose() {
		update()
		draw()
	}

	rl.CloseWindow()
}

func update() {
	if rl.IsMouseButtonDown(rl.MouseRightButton) {
		delta := rl.GetMouseDelta()
		delta = rl.Vector2Scale(delta, -1.0/camera.Zoom)

		camera.Target = rl.Vector2Add(camera.Target, delta)
	}

	// Zoom based on mouse wheel
	wheel := rl.GetMouseWheelMove()

	if wheel != 0 {
		// Get the world point that is under the mouse.
		mouseWorldPos := rl.GetScreenToWorld2D(rl.GetMousePosition(), camera)

		// Set the offset to where the mouse is.
		camera.Offset = rl.GetMousePosition()

		// Set the target to match, so that the camera maps the world space point
		// under the cursor to the screen space point under the cursor at any zoom
		camera.Target = mouseWorldPos

		camera.Zoom += (wheel * zoom_increment)

		if camera.Zoom < zoom_increment {
			camera.Zoom = zoom_increment
		}
	}
}

func draw() {
	rl.BeginDrawing()

	rl.ClearBackground(rl.Black)

	rl.BeginMode2D(camera)

	// Draw the 3d grid, rotated 90 degrees and centered around 0,0
	// just so we have something in the XY plane

	// Use `cgo` to call `raylib` functions that `raylib-go` doesn't have bindings for.
	C.rlPushMatrix()
	C.rlTranslatef(0, 25*50, 0)
	C.rlRotatef(90, 1, 0, 0)
	rl.DrawGrid(100, 50)
	C.rlPopMatrix()

	// Draw a reference circle
	rl.DrawCircle(100, 100, 50, rl.Yellow)

	rl.EndMode2D()

	rl.DrawText("Mouse right button drag to move, mouse wheel to zoom", 10, 10, 20, rl.White)

	rl.EndDrawing()
}

@FloatingSunfish
Copy link
Author

FloatingSunfish commented Oct 22, 2022

Hi, @JupiterRider.
Follow-up question. 🙂

I want to move the C functions into wrapper functions.
The following code works, but I'm concerned that I'm not calling C.free for the converted C.float variables:

func TranslateF(p0 float64, p1 float64, p2 float64) {
	C.rlTranslatef(C.float(p0), C.float(p1), C.float(p2))
}

I edited the code to call C.free for the converted C.float variables:

func TranslateF(p0 float64, p1 float64, p2 float64) {
	f0 := C.float(p0)
	pF0 := &f0
	defer C.free(unsafe.Pointer(pF0))

	f1 := C.float(p1)
	pF1 := &f1
	defer C.free(unsafe.Pointer(pF1))

	f2 := C.float(p2)
	pF2 := &f2
	defer C.free(unsafe.Pointer(pF2))

	C.rlTranslatef(f0, f1, f2)
}

However, when I use this version of the code, the window suddenly closes with the below Console output:

INFO: Initializing raylib 4.2
INFO: Supported raylib modules:
INFO:     > rcore:..... loaded (mandatory)
INFO:     > rlgl:...... loaded (mandatory)
INFO:     > rshapes:... loaded (optional) 
INFO:     > rtextures:. loaded (optional) 
INFO:     > rtext:..... loaded (optional) 
INFO:     > rmodels:... loaded (optional) 
INFO:     > raudio:.... loaded (optional) 
INFO: DISPLAY: Device initialized successfully
INFO:     > Display size: 1920 x 1080
INFO:     > Screen size:  800 x 450
INFO:     > Render size:  800 x 450
INFO:     > Viewport offsets: 0, 0
INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 390
INFO: GL: OpenGL device information:
INFO:     > Vendor:   NVIDIA Corporation
INFO:     > Renderer: NVIDIA GeForce GTX 1050 Ti/PCIe/SSE2
INFO:     > Version:  3.3.0 NVIDIA 522.25
INFO:     > GLSL:     3.30 NVIDIA via Cg compiler
INFO: GL: DXT compressed textures supported
INFO: GL: ETC2/EAC compressed textures supported
INFO: TEXTURE: [ID 1] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 1] Default texture loaded successfully
INFO: SHADER: [ID 1] Vertex shader compiled successfully
INFO: SHADER: [ID 2] Fragment shader compiled successfully
INFO: SHADER: [ID 3] Program shader loaded successfully
INFO: SHADER: [ID 3] Default shader loaded successfully
INFO: RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
INFO: RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
INFO: RLGL: Default OpenGL state initialized successfully
INFO: TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: TIMER: Target time per frame: 16.667 milliseconds
exit status 0xc0000374

Calling C.free for the converted C.float variables is causing issues.
However, I'm concerned that not doing so is going to cause a memory leak because I'm using C.float instead of a Go type.

Any advice?

@gen2brain
Copy link
Owner

@FloatingSunfish You don't need to free C.float, you are only doing type conversion, and you did not allocate anything.
See for example if you are to allocate a new C string, then you must free:

        cfileName := C.CString(fileName)
        defer C.free(unsafe.Pointer(cfileName))

@FloatingSunfish
Copy link
Author

Noted, thanks @gen2brain! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants