Skip to content

Commit 30c8c9f

Browse files
committed
Add option to take into account adjust mode when translating from world to screen coordinates
Fixes #23
1 parent be15c0f commit 30c8c9f

File tree

5 files changed

+248
-8
lines changed

5 files changed

+248
-8
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,13 @@ Translate [screen boundaries](#screen-coordinates) (corners) to world coordinate
290290
* ```bounds``` (vector4) Screen bounds (x = left, y = top, z = right, w = bottom)
291291

292292

293-
### camera.world_to_screen(camera_id, world)
294-
Translate world coordinates to [screen coordinates](#screen-coordinates), based on the view and projection of the camera. This is useful when manually culling game objects and you need to determine if a world coordinate will be visible or not.
293+
### camera.world_to_screen(camera_id, world, [adjust_mode])
294+
Translate world coordinates to [screen coordinates](#screen-coordinates), based on the view and projection of the camera, optionally taking into account an adjust mode. This is useful when manually culling game objects and you need to determine if a world coordinate will be visible or not. It can also be used to position gui nodes on top of game objects.
295295

296296
**PARAMETER**
297297
* ```camera_id``` (hash|url)
298298
* ```world``` (vector3) World coordinates to convert
299+
* ```adjust_mode``` (number) One of gui.ADJUST_FIT, gui.ADJUST_ZOOM and gui.ADJUST_STRETCH, or nil to not take into account the adjust mode.
299300

300301
**RETURN**
301302
* ```screen_coords``` (vector3) Screen coordinates

example/camera_controls.gui

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,6 +2941,192 @@ nodes {
29412941
text_leading: 1.0
29422942
text_tracking: 0.0
29432943
}
2944+
nodes {
2945+
position {
2946+
x: 0.0
2947+
y: 0.0
2948+
z: 0.0
2949+
w: 1.0
2950+
}
2951+
rotation {
2952+
x: 0.0
2953+
y: 0.0
2954+
z: 0.0
2955+
w: 1.0
2956+
}
2957+
scale {
2958+
x: 1.0
2959+
y: 1.0
2960+
z: 1.0
2961+
w: 1.0
2962+
}
2963+
size {
2964+
x: 200.0
2965+
y: 100.0
2966+
z: 0.0
2967+
w: 1.0
2968+
}
2969+
color {
2970+
x: 1.0
2971+
y: 0.0
2972+
z: 0.0
2973+
w: 1.0
2974+
}
2975+
type: TYPE_TEXT
2976+
blend_mode: BLEND_MODE_ALPHA
2977+
text: "STRETCH"
2978+
font: "silkscreen"
2979+
id: "w2s_stretch"
2980+
xanchor: XANCHOR_NONE
2981+
yanchor: YANCHOR_NONE
2982+
pivot: PIVOT_CENTER
2983+
outline {
2984+
x: 1.0
2985+
y: 1.0
2986+
z: 1.0
2987+
w: 1.0
2988+
}
2989+
shadow {
2990+
x: 1.0
2991+
y: 1.0
2992+
z: 1.0
2993+
w: 1.0
2994+
}
2995+
adjust_mode: ADJUST_MODE_STRETCH
2996+
line_break: false
2997+
layer: "text"
2998+
inherit_alpha: true
2999+
alpha: 1.0
3000+
outline_alpha: 1.0
3001+
shadow_alpha: 1.0
3002+
template_node_child: false
3003+
text_leading: 1.0
3004+
text_tracking: 0.0
3005+
}
3006+
nodes {
3007+
position {
3008+
x: 0.0
3009+
y: 0.0
3010+
z: 0.0
3011+
w: 1.0
3012+
}
3013+
rotation {
3014+
x: 0.0
3015+
y: 0.0
3016+
z: 0.0
3017+
w: 1.0
3018+
}
3019+
scale {
3020+
x: 1.0
3021+
y: 1.0
3022+
z: 1.0
3023+
w: 1.0
3024+
}
3025+
size {
3026+
x: 200.0
3027+
y: 100.0
3028+
z: 0.0
3029+
w: 1.0
3030+
}
3031+
color {
3032+
x: 0.0
3033+
y: 1.0
3034+
z: 0.0
3035+
w: 1.0
3036+
}
3037+
type: TYPE_TEXT
3038+
blend_mode: BLEND_MODE_ALPHA
3039+
text: "ZOOM"
3040+
font: "silkscreen"
3041+
id: "w2s_zoom"
3042+
xanchor: XANCHOR_NONE
3043+
yanchor: YANCHOR_NONE
3044+
pivot: PIVOT_CENTER
3045+
outline {
3046+
x: 1.0
3047+
y: 1.0
3048+
z: 1.0
3049+
w: 1.0
3050+
}
3051+
shadow {
3052+
x: 1.0
3053+
y: 1.0
3054+
z: 1.0
3055+
w: 1.0
3056+
}
3057+
adjust_mode: ADJUST_MODE_ZOOM
3058+
line_break: false
3059+
layer: "text"
3060+
inherit_alpha: true
3061+
alpha: 1.0
3062+
outline_alpha: 1.0
3063+
shadow_alpha: 1.0
3064+
template_node_child: false
3065+
text_leading: 1.0
3066+
text_tracking: 0.0
3067+
}
3068+
nodes {
3069+
position {
3070+
x: 0.0
3071+
y: 0.0
3072+
z: 0.0
3073+
w: 1.0
3074+
}
3075+
rotation {
3076+
x: 0.0
3077+
y: 0.0
3078+
z: 0.0
3079+
w: 1.0
3080+
}
3081+
scale {
3082+
x: 1.0
3083+
y: 1.0
3084+
z: 1.0
3085+
w: 1.0
3086+
}
3087+
size {
3088+
x: 200.0
3089+
y: 100.0
3090+
z: 0.0
3091+
w: 1.0
3092+
}
3093+
color {
3094+
x: 0.0
3095+
y: 0.0
3096+
z: 1.0
3097+
w: 1.0
3098+
}
3099+
type: TYPE_TEXT
3100+
blend_mode: BLEND_MODE_ALPHA
3101+
text: "FIT"
3102+
font: "silkscreen"
3103+
id: "w2s_fit"
3104+
xanchor: XANCHOR_NONE
3105+
yanchor: YANCHOR_NONE
3106+
pivot: PIVOT_CENTER
3107+
outline {
3108+
x: 1.0
3109+
y: 1.0
3110+
z: 1.0
3111+
w: 1.0
3112+
}
3113+
shadow {
3114+
x: 1.0
3115+
y: 1.0
3116+
z: 1.0
3117+
w: 1.0
3118+
}
3119+
adjust_mode: ADJUST_MODE_FIT
3120+
line_break: false
3121+
layer: "text"
3122+
inherit_alpha: true
3123+
alpha: 1.0
3124+
outline_alpha: 1.0
3125+
shadow_alpha: 1.0
3126+
template_node_child: false
3127+
text_leading: 1.0
3128+
text_tracking: 0.0
3129+
}
29443130
layers {
29453131
name: "below"
29463132
}

example/camera_controls.gui_script

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ end
3333
function update(self, dt)
3434
camera.set_window_scaling_factor(get_scaling_factor())
3535

36+
local w2s_fit = gui.get_node("w2s_fit")
37+
local w2s_zoom = gui.get_node("w2s_zoom")
38+
local w2s_stretch = gui.get_node("w2s_stretch")
39+
local world_pos = vmath.vector3(160, 160, 0)
40+
local screen_pos_stretch = camera.world_to_screen(CAMERA_ID, world_pos, gui.get_adjust_mode(w2s_stretch))
41+
local screen_pos_fit = camera.world_to_screen(CAMERA_ID, world_pos, gui.get_adjust_mode(w2s_fit))
42+
local screen_pos_zoom = camera.world_to_screen(CAMERA_ID, world_pos, gui.get_adjust_mode(w2s_zoom))
43+
gui.set_position(w2s_fit, screen_pos_fit)
44+
gui.set_position(w2s_zoom, screen_pos_zoom)
45+
gui.set_position(w2s_stretch, screen_pos_stretch)
46+
3647
local bounds = camera.screen_to_world_bounds(CAMERA_ID)
3748
gui.set_text(gui.get_node("bottom_left"), ("%d,%d"):format(bounds.x, bounds.w))
3849
gui.set_text(gui.get_node("bottom_right"), ("%d,%d"):format(bounds.z, bounds.w))

example/top_down.tilemap

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3786,7 +3786,7 @@ layers {
37863786
}
37873787
layers {
37883788
id: "walls"
3789-
z: 0.0
3789+
z: 0.1
37903790
is_visible: 1
37913791
cell {
37923792
x: 0
@@ -4005,6 +4005,13 @@ layers {
40054005
h_flip: 0
40064006
v_flip: 0
40074007
}
4008+
cell {
4009+
x: 2
4010+
y: 2
4011+
tile: 206
4012+
h_flip: 0
4013+
v_flip: 0
4014+
}
40084015
cell {
40094016
x: 12
40104017
y: 2

orthographic/camera.lua

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ local WINDOW_WIDTH = DISPLAY_WIDTH
2323
local WINDOW_HEIGHT = DISPLAY_HEIGHT
2424

2525

26+
local GUI_ADJUST = {
27+
[gui.ADJUST_FIT] = {sx=1, sy=1, ox=0, oy=0}, -- Fit
28+
[gui.ADJUST_ZOOM] = {sx=1, sy=1, ox=0, oy=0}, -- Zoom
29+
[gui.ADJUST_STRETCH] = {sx=1, sy=1, ox=0, oy=0}, -- Stretch
30+
}
31+
2632
-- center camera to middle of screen
2733
local OFFSET = vmath.vector3(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, 0)
2834

@@ -125,6 +131,31 @@ function M.set_window_size(width, height)
125131
assert(height, "You must provide window height")
126132
WINDOW_WIDTH = width
127133
WINDOW_HEIGHT = height
134+
135+
local sx = WINDOW_WIDTH / DISPLAY_WIDTH
136+
local sy = WINDOW_HEIGHT / DISPLAY_HEIGHT
137+
138+
-- Fit
139+
local adjust = GUI_ADJUST[gui.ADJUST_FIT]
140+
local scale = math.min(sx, sy)
141+
adjust.sx = scale
142+
adjust.sy = scale
143+
adjust.ox = (WINDOW_WIDTH - DISPLAY_WIDTH * scale) * 0.5 / scale
144+
adjust.oy = (WINDOW_HEIGHT - DISPLAY_HEIGHT * scale) * 0.5 / scale
145+
146+
-- Zoom
147+
adjust = GUI_ADJUST[gui.ADJUST_ZOOM]
148+
scale = math.max(sx, sy)
149+
adjust.sx = scale
150+
adjust.sy = scale
151+
adjust.ox = (WINDOW_WIDTH - DISPLAY_WIDTH * scale) * 0.5 / scale
152+
adjust.oy = (WINDOW_HEIGHT - DISPLAY_HEIGHT * scale) * 0.5 / scale
153+
154+
-- Stretch
155+
adjust = GUI_ADJUST[gui.ADJUST_STRETCH]
156+
adjust.sx = sx
157+
adjust.sy = sy
158+
-- distorts to fit window, offsets always zero
128159
end
129160

130161
--- Get the window size
@@ -540,19 +571,23 @@ function M.window_to_world(camera_id, window)
540571
return M.unproject(view, projection, screen)
541572
end
542573

543-
544574
--- Convert world coordinates to screen coordinates based
545575
-- on a specific camera's view and projection.
546576
-- @param camera_id
547577
-- @param world World coordinates as a vector3
548578
-- @return Screen coordinates
549579
-- http://webglfactory.blogspot.se/2011/05/how-to-convert-world-to-screen.html
550-
function M.world_to_screen(camera_id, world)
580+
function M.world_to_screen(camera_id, world, adjust_mode)
551581
assert(camera_id, "You must provide a camera id")
552582
assert(world, "You must provide world coordinates to convert")
553583
local view = cameras[camera_id].view or MATRIX4
554584
local projection = cameras[camera_id].projection or MATRIX4
555-
return M.project(view, projection, vmath.vector3(world))
585+
local screen = M.project(view, projection, vmath.vector3(world))
586+
if adjust_mode then
587+
screen.x = screen.x / GUI_ADJUST[adjust_mode].sx - GUI_ADJUST[adjust_mode].ox
588+
screen.y = screen.y / GUI_ADJUST[adjust_mode].sy - GUI_ADJUST[adjust_mode].oy
589+
end
590+
return vmath.vector3(screen.x, screen.y, screen.z)
556591
end
557592

558593

@@ -569,8 +604,8 @@ function M.project(view, projection, world)
569604
assert(world, "You must provide world coordinates to translate")
570605
v4_tmp.x, v4_tmp.y, v4_tmp.z, v4_tmp.w = world.x, world.y, world.z, 1
571606
local v4 = projection * view * v4_tmp
572-
world.x = ((v4.x + 1) / 2) * DISPLAY_WIDTH
573-
world.y = ((v4.y + 1) / 2) * DISPLAY_HEIGHT
607+
world.x = ((v4.x + 1) / 2) * WINDOW_WIDTH
608+
world.y = ((v4.y + 1) / 2) * WINDOW_HEIGHT
574609
world.z = ((v4.z + 1) / 2)
575610
return world
576611
end

0 commit comments

Comments
 (0)