(Godot Engine) Replicating Line2D's joint functionality on the first + last points - geometry

I have created the following method to outline an 2D polygon using an Line2D node (in favor of _drawing because of texturing and round jointing capabilities of the Line2D node):
func Set_Polygon_Outline(_polygon_node: Node2D, _width: int = 5, _color: Color = Color.black, _texture: Texture = null) -> void:
if _polygon_node is Polygon2D:
var _polygon: PoolVector2Array = (_polygon_node as Polygon2D).polygon
if _polygon.size() >= 3:
# Line2D node setup
var _line_node: Line2D = null
var _line_name: String = str(_polygon_node.name, "_Line")
if not _polygon_node.has_node(_line_name):
_line_node = Line2D.new() ; _line_node.name = _line_name ; _polygon_node.add_child(_line_node)
else: _line_node = _polygon_node.get_node(_line_name) as Line2D
# Line2D properties setup
if _line_node != null:
_line_node.width = _width ; _line_node.default_color = _color ; _line_node.joint_mode = Line2D.LINE_JOINT_ROUND
if _texture != null:
_line_node.texture = _texture ; _line_node.texture_mode = Line2D.LINE_TEXTURE_STRETCH
var _points: PoolVector2Array = _polygon ; _points.append(_polygon[0]) ; _line_node.points = _points
How would it be possible to replicate the round point jointing on point 0 in the same way as the other points?
The result meets expectations except on the closing points (from 4 to 0)
One approach I have tried is appending an additional point (point 1) to the _points Array. While the un-textured one looks as desired, the textured variant is slightly off on the additional line because of the two superimposing textures with alpha values making it look "bolder".
Another (very unorthodox approach) is creating two polygons: one black and one with an blur shader, using the following method:
func Set_Polygon_Shadow(_polygon_node: Node2D, _size: float = 10.0, _color: Color = Color.black) -> void:
if _polygon_node is Polygon2D:
var _polygon: PoolVector2Array = (_polygon_node as Polygon2D).polygon
if _polygon.size() >= 3:
# Shadow Polygon node setup
var _shadow_name: String = str(_polygon_node.name, "_Shadow")
if not _polygon_node.has_node(_shadow_name):
var _shadow_node: Polygon2D = Polygon2D.new()
_shadow_node.polygon = Geometry.offset_polygon_2d(_polygon, _size).front() ; _shadow_node.color = _color
_shadow_node.show_behind_parent = true ; _polygon_node.add_child(_shadow_node)
# Blur Polygon node setup
var _blur_node: Polygon2D = Polygon2D.new()
_blur_node.polygon = Geometry.offset_polygon_2d(_polygon, _size * 2.0).front()
_blur_node.material = ShaderMaterial.new()
_blur_node.material.shader = preload("res://shaders/Shader_Blur.shader")
_blur_node.material.set_shader_param("Strength", 2.0)
_blur_node.show_behind_parent = true ; _polygon_node.add_child(_blur_node)
The shader code:
shader_type canvas_item;
uniform float Strength : hint_range(0.0, 5.0);
void fragment() {COLOR = textureLod(SCREEN_TEXTURE, SCREEN_UV, Strength);}
The result looks good but I can't possibly imagine using this approach on a large number of polygons.
Thank you kindly,
Mike.

If using a Line2D is a viable solution, except for fixing the loop, then let us fix the loop.
For a Line2D to loop seamless, even with transparency, it must close in a straight segment (not a corner). And have no end caps.
Thus, I suggest to move the first point, half way between its original position and the second point. Then you add at the end the original position of the first point, following by a copy of its moved position...
Like this:
# Let o be a copy of the original first point
var o = _points[0]
# Let m be the middle of the straight segment between the first and second points
var m = o + (_points[1] - o) * 0.5
_points[0] = m # The line now starts in m, we are moving the first point forth
_points.append(o) # Since we moved the first point forth, add its original position back
_points.append(m) # Add m at the end, so it loops, in the middle of a straight segment
That should result in a Line2D that loops seamlessly.

Related

Godot smooth transition between players

I have a "parent" player scene, and I inherit scenes for each player. The parent player scene has a camera. When the game switches between players, one player turns off its camera, and the other player turns its camera on:
if state != State.ACTIVE:
# If this player is becoming active, also
# set camera current
state = State.ACTIVE
camera.current = true
else:
# If player is not becoming active,
# disable this players camera
camera.current = false
But players can be in different positions, so the camera "jumps" from one to the other. Can we do something more sophisticated, like set the new camera to the current position so the smooth setting can be used to handle the transition?
One idea is to do get_viewport().get_camera() to find the current position of the camera to try and sync the position of the current camera with the new camera that is about to turn on, but appears to not work for 2D scenes. CF: https://github.com/godotengine/godot/pull/38317
Sadly, as you found out, there is no way to get the current Camera2D in Godot 3.x. And you found the pull request that adds the feature to Godot 4.0.
What I'm going to suggest is to have one sole Camera2D, so that one is always the current one. And you can define Position2D inside your scenes that can serve as interpolation targets to move the Camera2D.
I have an script that I think will be useful for you (I made it to be RemoteTransform2D but backwards, it does push a transform, it pulls it), I call it anchor_transform_2d.gd:
tool
class_name AnchorTransform2D
extends Node2D
export var anchor_path:NodePath setget set_anchor_path
export var reference_path:NodePath setget set_reference_path
export var set_local_transform:bool
export(int, FLAGS, "x", "y") var translation_mode:int
export(int, FLAGS, "x", "y") var scale_mode:int
export var rotation_mode:bool
var _anchor:Node2D
var _reference:Node2D
func _physics_process(_delta: float) -> void:
if not is_instance_valid(_anchor) or Engine.editor_hint:
set_physics_process(false)
return
#INPUT
var input := _anchor.global_transform
if is_instance_valid(_reference):
input = _reference.global_transform.affine_inverse() * input
#TRANSLATION
var origin := Vector2 (
input.origin.x if translation_mode & 1 else 0.0,
input.origin.y if translation_mode & 2 else 0.0
)
#ROTATION
var angle := 0.0
if rotation_mode:
angle = input.get_rotation()
#SCALE
var source_scale = input.get_scale()
var scaling := Vector2 (
source_scale.x if scale_mode & 16 else 1.0,
source_scale.y if scale_mode & 32 else 1.0
)
#RESULT
_set_target_transform(
Transform2D(angle, origin) * Transform2D.IDENTITY.scaled(scaling)
)
func set_anchor_path(new_value:NodePath) -> void:
anchor_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_anchor = get_node_or_null(anchor_path) as Node2D
set_physics_process(is_instance_valid(_anchor) and not Engine.editor_hint)
if Engine.editor_hint:
update_configuration_warning()
func set_reference_path(new_value:NodePath) -> void:
reference_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_reference = get_node_or_null(reference_path) as Node2D
func _set_target_transform(new_value:Transform2D) -> void:
if set_local_transform:
transform = new_value
return
global_transform = new_value
func _get_configuration_warning() -> String:
if _anchor == null:
return "Anchor not found"
return ""
Add this attached to a Node2D in anchor_path set the target from which you want to pull the transform (anchor_path is a NodePath, you can set to it something like $Position2D.get_path()). And set what do you want to copy (you can choose any combination of position x, position y, scaling x, scaling y, and rotation). Then put the Camera2D as a child of the AnchorTransform2D, and set smoothing_enabled to true.
Rundown of the properties:
anchor_path: A NodePath pointing to the Node2D you want to pull the transform from.
reference_path: A NodePath pointing to a Node2D used to make the transform relative (you will be taking the transform of what you put in anchor_path relative to what you put in reference_path).
set_local_transform: Set to true if you want to pull the transform as local (relative to the parent of AnchorTransform2D), leave to false to set the global transform instead.
translation_mode: Specifies if you are going to copy the x position, y position, both or neither.
scale_mode: Specifies if you are going to copy the x scale, y scale, both or neither.
rotation_mode: Specifies if you are going to copy the rotation or not.
The only reason the script is a tool script is to give you a warning in the editor if you forgot to set the anchor_path.

Running cv::warpPerspective on points

I'm running the cv::warpPerspective() function on a image and what to get the position of the some points of result image the I get in the source image, here how far I came :
int main (){
cv::Point2f srcQuad[4],dstQuad[4];
cv::Mat warpMatrix;
cv::Mat src, dst,src2;
src = cv::imread("card.jpg",1);
srcQuad[0].x = 0; //src Top left
srcQuad[0].y = 0;
srcQuad[1].x = src.cols - 1; //src Top right
srcQuad[1].y = 0;
srcQuad[2].x = 0; //src Bottom left
srcQuad[2].y = src.rows - 1;
srcQuad[3].x = src.cols -1; //src Bot right
srcQuad[3].y = src.rows - 1;
dstQuad[0].x = src.cols*0.05; //dst Top left
dstQuad[0].y = src.rows*0.33;
dstQuad[1].x = src.cols*0.9; //dst Top right
dstQuad[1].y = src.rows*0.25;
dstQuad[2].x = src.cols*0.2; //dst Bottom left
dstQuad[2].y = src.rows*0.7;
dstQuad[3].x = src.cols*0.8; //dst Bot right
dstQuad[3].y = src.rows*0.9;
warpMatrix =cv::getPerspectiveTransform(srcQuad,dstQuad);
cv::warpPerspective(src,dst,warpMatrix,src.size());
cv::imshow("source", src);
cv::imshow("destination", dst);
cv::warpPerspective(dst,src2,warpMatrix,dst.size(),CV_WARP_INVERSE_MAP);
cv::imshow("srouce 2 " , src2);
cv::waitKey();
return 0;
my problem is that if I select a point from dst how can get its coordinates in ** src or src2 ** since the cv::warpPerspective function doesn't take cv::Point as parameter ??
You want perspectiveTransform (which works on a vector of Points) rather than warpPerspective.
Take the inverse of warpMatrix; you may have to tweak the final column.
vector<Point2f> dstPoints, srcPoints;
dstPoints.push_back(Point2f(1,1));
cv::perspectiveTransform(dstPoints,srcPoints,warpMatrix.inv());
A perspective transform relates two points in the following manner:
[x'] [m00 m01 m02] [x]
[y'] = [m10 m11 m12] [y]
[1] [m20 m21 m22] [1]
Where (x,y) are the original 2D point coordinates, and (x', y') are the transformed coordinates.
In your case, you know (x', y'), and want to know (x, y). This can be achieved by multiplying the known point by the inverse of the transformation matrix:
cv::Matx33f warp = warpMatrix; // cv::Matx is much more useful for math
cv::Point2f warped_point = dstQuad[3]; // I just use dstQuad as an example
cv::Point3f homogeneous = warp.inv() * warped_point;
cv::Point2f result(homogeneous.x, homogeneous.y); // Drop the z=1 to get out of homogeneous coordinates
// now, result == srcQuad[3], which is what you wanted

D3 JS upside down path text

Is it possible to show the text not upside down in this case?
http://jsfiddle.net/paulocoelho/Hzsm8/1/
Code:
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",35)
.append("textPath")
.attr("xlink:href","#yyy")
.attr("startOffset",50)
.text("some text")
;
A great example is Placing Texts on Arcs with D3.js by Nadieh Bremer. A lengthy blog with many images from which the following is an extract:
Flipping the Text on the Bottom Half
You could already feel like it’s finished with that look. But I find those labels along the bottom half, that are upside down, rather hard to read. I’d prefer it if those labels were flipped, so I can read them from left to right again.
To accomplish this, we need to switch the start and end coordinates of the current arc paths along the bottom half so they are drawn from left to right. Furthermore, the sweep-flag has to be set to 0 to get the arc that runs in a counterclockwise fashion from left to right
So for the final act, let’s add a few more lines of code to the .each() statement
//Create the new invisible arcs and flip the direction for those labels on the bottom half
.each(function(d,i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the commas so that IE can handle it
newArc = newArc.replace(/,/g , " ");
//If the end angle lies beyond a quarter of a circle (90 degrees or pi/2)
//flip the end and start position
if (d.endAngle > 90 * Math.PI/180) {
var startLoc = /M(.*?)A/, //Everything between the capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the 0 0 1 and the end of the string (denoted by $)
//Flip the direction of the arc by switching the start and end point (and sweep flag)
var newStart = endLoc.exec( newArc )[1];
var newEnd = startLoc.exec( newArc )[1];
var middleSec = middleLoc.exec( newArc )[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
}//if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", "donutArc"+i)
.attr("d", newArc)
.style("fill", "none");
});
The only thing that has changed since the previous section is the addition of the if statement. To flip the start and end positions, we can use a few more regular expressions. The current starting x and y location is given by everything in between the capital M and the capital A. The current radius is denoted by everything in between the capital A and the 0 0 1 of the x-axis rotation, large-arc flag and sweep flag. Finally the end location is given by all in between the 0 0 1 and the end of the string (denoted by a $ in regex).
So we save all the pieces in different variables and build/replace up the newArc using the final line in the if statement which has switched the start and end position.
The textPath section needs a small change. For the bottom half arcs, the dy attribute shouldn’t raise the labels above the arc paths, but lower the labels below the arc paths. So we need a small if statement which will result in two different dy values.
(To be able to use the d.endAngle in the if statement I replaced the donutData by pie(donutData) in the .data() step. You can still reference the data itself by using d.data instead of just d, which you can see in the .text() line of code.)
//Append the label names on the outside
svg.selectAll(".donutText")
.data(pie(donutData))
.enter().append("text")
.attr("class", "donutText")
//Move the labels below the arcs for those slices with an end angle greater than 90 degrees
.attr("dy", function(d,i) { return (d.endAngle > 90 * Math.PI/180 ? 18 : -11); })
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#donutArc"+i;})
.text(function(d){return d.data.name;});
It looks like when d3 creates one of those filled arcs it actually creates a filled path shape that always(?) starts on the right and proceeds clockwise - even if you reverse startAngle and endAngle.
If you manually create your own arc path, and put your text on that, you can get it to do the right thing.
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
// Radius of line text sits on. A value of 3.5 makes it slightly closer to the
// outer radius (so text is placed in the middle of the blue line).
var textpathRadius = (cfg.h / 3.5);
// Make a path for the text to sit on that goes in an anti-clockwise direction.
var textpath = g.append("svg:path")
.attr("id","zzz")
.style("display","none")
.attr("d", "M -"+textpathRadius+" 0 A "+textpathRadius+" "+textpathRadius+" 0 0 0 "+textpathRadius+" 0")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#zzz")
.attr("startOffset","50%")
.style("text-anchor","middle")
.text("some text");
I've never used d3 before so there might be an easier or cleaner way to do what I've done. But at least it should give you a place to start.
Updated fiddle: http://jsfiddle.net/3DfVD/

Corona SDK how to remove an object?

Well i have some trouble removing an object from the game, the thing is that i have a player class (made out of a metatable), inside it i have a variable called sprite that holds the address for the image sprite i will draw onScreen, so when i create the object i don't draw the sprite right away, for that i made a function draw (this is just to explain what i have). in the game.lua i draw the player by calling that function, and afterwards i want to delete my instance of player (so that way the image onscreen dissapears also)... thats all, i tried player:removeSelf(), display.remove(player) and one of them threw me an error (attemp to call field 'removeSelf' (a nil value)) and the other one runs fine but it doesn't change the fact that the player is still there (i can acces it's functions and the sprite is still shown onscreen... well here is my code:
**********************************************************
game.lua:
**********************************************************
---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------
local _W, _H = display.contentWidth * 0.5, display.contentHeight * 0.5
local background, player, land
local spriteSizeX, spriteSizeY = 60,70
-- Called when the scene's view does not exist:
function scene:createScene( event )
local group = self.view
local BG = display.newGroup()
local stage = display.newGroup()
local foreground = display.newGroup()
group:insert(BG)
group:insert(stage)
group:insert(foreground)
-----------------------------------------------------------------------------
-- CREATE display objects and add them to 'group' here.
-- Example use-case: Restore 'group' from previously saved state.
-----------------------------------------------------------------------------
background = display.newImage("assets/backgrounds/oliveBackground.png", true)
background.x, background.y = _W, _H
BG:insert( background )
player = playerClass.new()
player:draw(15,57.5,foreground)
--player:movePlayer(300,140)
terrain = {}
local m,j = 0,0
for i = 1, 16 do
local l = 1
for k = 3, 10 do
land = landClass.new({posx = i, posy = k})
table.insert(terrain, land)
m = m +1
terrain[m]:draw( spriteSizeX/4 + ((spriteSizeX/2) * j), spriteSizeY/4 + ((spriteSizeY/2) * l) + 5, stage)
l = l + 1
end
j = j+1
end
-- remove an asset doesn't work
--display.remove(terrain[1]:getSprite())
--terrain[1].removeSelf()
-- terrain[1] = nil
player:destroy()
end
**********************************************************
player.lua:
**********************************************************
-- player class
local player = {}
local player_mt = { __index = player }
--[[
-- attributes
local sprite, coins, speed
]]--
function player.new() -- constructor
local newPlayer = {
sprite = "assets/char/miner.png",
coins = 1000,
speed = 1
}
return setmetatable( newPlayer, player_mt )
end
-- local function, works only when called from inside this class
local function getName()
-- print("")
end
function player:draw(x,y,group)
sprite = display.newImage(self.sprite)
sprite.x, sprite.y = x, y
sprite.xScale, sprite.yScale = 0.5, 0.5
group:insert(sprite)
end
function player:movePlayer(posx,posy)
transition.to(sprite, { x = posx, y = posy, time=500, delay=0, alpha=1.0 })
end
function player:destroy()
-- none of them work
-- self.sprite = nil
-- self.sprite.removeSelf()
end
return player
After creating your sprite using display.newImage, you did not store it in the instance.
self.sprite just has the string value "assets/char/miner.png"
in your draw function add
self.spriteObject = sprite
and in your destroy function ,
self.spriteObject:removeSelf()
You can use one of these functions for your related object:
removeSelf()
destroy()

How to compute the visible area based on a heightmap?

I have a heightmap. I want to efficiently compute which tiles in it are visible from an eye at any given location and height.
This paper suggests that heightmaps outperform turning the terrain into some kind of mesh, but they sample the grid using Bresenhams.
If I were to adopt that, I'd have to do a line-of-sight Bresenham's line for each and every tile on the map. It occurs to me that it ought to be possible to reuse most of the calculations and compute the heightmap in a single pass if you fill outwards away from the eye - a scanline fill kind of approach perhaps?
But the logic escapes me. What would the logic be?
Here is a heightmap with a the visibility from a particular vantagepoint (green cube) ("viewshed" as in "watershed"?) painted over it:
Here is the O(n) sweep that I came up with; I seems the same as that given in the paper in the answer below How to compute the visible area based on a heightmap? Franklin and Ray's method, only in this case I am walking from eye outwards instead of walking the perimeter doing a bresenhams towards the centre; to my mind, my approach would have much better caching behaviour - i.e. be faster - and use less memory since it doesn't have to track the vector for each tile, only remember a scanline's worth:
typedef std::vector<float> visbuf_t;
inline void map::_visibility_scan(const visbuf_t& in,visbuf_t& out,const vec_t& eye,int start_x,int stop_x,int y,int prev_y) {
const int xdir = (start_x < stop_x)? 1: -1;
for(int x=start_x; x!=stop_x; x+=xdir) {
const int x_diff = abs(eye.x-x), y_diff = abs(eye.z-y);
const bool horiz = (x_diff >= y_diff);
const int x_step = horiz? 1: x_diff/y_diff;
const int in_x = x-x_step*xdir; // where in the in buffer would we get the inner value?
const float outer_d = vec2_t(x,y).distance(vec2_t(eye.x,eye.z));
const float inner_d = vec2_t(in_x,horiz? y: prev_y).distance(vec2_t(eye.x,eye.z));
const float inner = (horiz? out: in).at(in_x)*(outer_d/inner_d); // get the inner value, scaling by distance
const float outer = height_at(x,y)-eye.y; // height we are at right now in the map, eye-relative
if(inner <= outer) {
out.at(x) = outer;
vis.at(y*width+x) = VISIBLE;
} else {
out.at(x) = inner;
vis.at(y*width+x) = NOT_VISIBLE;
}
}
}
void map::visibility_add(const vec_t& eye) {
const float BASE = -10000; // represents a downward vector that would always be visible
visbuf_t scan_0, scan_out, scan_in;
scan_0.resize(width);
vis[eye.z*width+eye.x-1] = vis[eye.z*width+eye.x] = vis[eye.z*width+eye.x+1] = VISIBLE;
scan_0.at(eye.x) = BASE;
scan_0.at(eye.x-1) = BASE;
scan_0.at(eye.x+1) = BASE;
_visibility_scan(scan_0,scan_0,eye,eye.x+2,width,eye.z,eye.z);
_visibility_scan(scan_0,scan_0,eye,eye.x-2,-1,eye.z,eye.z);
scan_out = scan_0;
for(int y=eye.z+1; y<height; y++) {
scan_in = scan_out;
_visibility_scan(scan_in,scan_out,eye,eye.x,-1,y,y-1);
_visibility_scan(scan_in,scan_out,eye,eye.x,width,y,y-1);
}
scan_out = scan_0;
for(int y=eye.z-1; y>=0; y--) {
scan_in = scan_out;
_visibility_scan(scan_in,scan_out,eye,eye.x,-1,y,y+1);
_visibility_scan(scan_in,scan_out,eye,eye.x,width,y,y+1);
}
}
Is it a valid approach?
it is using centre-points rather than looking at the slope between the 'inner' pixel and its neighbour on the side that the LoS passes
could the trig in to scale the vectors and such be replaced by factor multiplication?
it could use an array of bytes since the heights are themselves bytes
its not a radial sweep, its doing a whole scanline at a time but away from the point; it only uses only a couple of scanlines-worth of additional memory which is neat
if it works, you could imagine that you could distribute it nicely using a radial sweep of blocks; you have to compute the centre-most tile first, but then you can distribute all immediately adjacent tiles from that (they just need to be given the edge-most intermediate values) and then in turn more and more parallelism.
So how to most efficiently calculate this viewshed?
What you want is called a sweep algorithm. Basically you cast rays (Bresenham's) to each of the perimeter cells, but keep track of the horizon as you go and mark any cells you pass on the way as being visible or invisible (and update the ray's horizon if visible). This gets you down from the O(n^3) of the naive approach (testing each cell of an nxn DEM individually) to O(n^2).
More detailed description of the algorithm in section 5.1 of this paper (which you might also find interesting for other reasons if you aspire to work with really enormous heightmaps).

Resources