Can someone who has experience with compute shaders explain what is an id parameter?
void CSMain (uint3 id : SV_DispatchThreadID)
I believe it's an index, but an index of what?
How do you manipulate certain texture pixels in compute shader? How do you access them?
Let's say I have an 8x8 image and whenever I click on the texture I want to colorize the clicked on pixel and its surrounding pixels depending on the radius.
E.g.
I have created this simple EditorWindow, where I listen for clicks and if I click I Dispatch the compute shader to process all the data
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
public class DrawingEditor : EditorWindow
{
bool pressed = false;
ComputeShader computeShader = null;
RenderTexture dest;
bool dragged = false;
int radius = 5;
Color color = Color.black;
[MenuItem("Window/Drawing Editor")]
public static void Open()
{
var window = EditorWindow.GetWindow<DrawingEditor>();
window.ShowUtility();
}
private void OnEnable()
{
dest = new RenderTexture(1200, 514, 0, RenderTextureFormat.ARGB32, 0);
dest.enableRandomWrite = true; dest.Create();
}
private void OnGUI()
{
var tex = new Rect(0, 0, 350, 150);
GUI.DrawTexture(tex, dest);
var shader = new Rect(25, tex.y + tex.height + 25, tex.width - 50, 17);
computeShader = (ComputeShader)EditorGUI.ObjectField(shader, new GUIContent("Shader"), computeShader, typeof(ComputeShader), allowSceneObjects: false);
var rad = new Rect(25, shader.y + shader.height + 25, tex.width - 50, 17);
radius = EditorGUI.IntSlider(rad, radius, 1, 50);
var col = new Rect(25, rad.y + rad.height + 25, tex.width - 50, 17);
color = EditorGUI.ColorField(col, color);
var e = Event.current; bool paint = false;
if (e.rawType == EventType.MouseDown)
{
pressed = true;
}
else if (e.rawType == EventType.MouseUp && pressed)
{
if (!dragged)
{
paint = true;
}
pressed = false; dragged = false;
}
if (e.rawType == EventType.MouseDrag && pressed)
{
paint = true;
dragged = true;
}
if (paint)
{
Paint(new Vector2Int(Convert.ToInt32(e.mousePosition.x), Convert.ToInt32(e.mousePosition.y)), tex);
}
}
void Paint(Vector2Int mousePos, Rect canvas)
{
var p = mousePos;
var xP = p.x / canvas.width;
var yP = p.y / canvas.height;
if (computeShader)
{
computeShader.SetTexture(0, "Result", dest);
computeShader.SetFloat("mouseX", dest.width * xP); computeShader.SetFloat("mouseY", dest.height * yP);
computeShader.SetInt("radius", radius);
computeShader.SetVector("color", color);
computeShader.SetInt("width", dest.width); computeShader.SetInt("height", dest.height);
computeShader.Dispatch(0, dest.width / 8, dest.height / 8, 1);
Repaint();
}
}
}
And this is the compute shader:
#pragma kernel CSMain
RWTexture2D<float4> Result;
float mouseX, mouseY;
int radius;
float4 color;
int width, height;
float4 Calc(uint x, uint y, float4 de)
{
if (x >= mouseX - ((float)radius / 2) && x < mouseX + radius)
{
if (y >= mouseY - ((float)radius / 2) && y < mouseY + radius)
{
return color;
}
}
return de;
}
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Result[id.xy] = Calc(id.x, id.y, Result[id.xy]);
}
Nothing is happening when I dispatch the compute shader, so I reckon I am doing something really wrong here.
How do you alter specific pixels? Can someone help me understand how things work with compute shaders?
Well since I haven't got any answers I did some trial and error and I managed to create something that works. I'm still miles away from fully grasping the compute shaders as a whole and how they work, but I'm slowly starting to understand a little bit.
This is the code I've used if anyone is interested:
#pragma kernel CSMain
RWTexture2D<float4> Result;
float mouseX, mouseY;
int radius;
vector<float, 4> color;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
if (id.x > mouseX - (radius / 2) && id.x < mouseX + (radius / 2) && id.y > mouseY - (radius / 2) && id.y < mouseY + (radius / 2))
Result[id.xy] = color;
}
Also, I passed mouse positions as floats which needed to be integers instead.
Related
Can anyone please help with the following?
Using Visual Studio 2019 Xamarin Forms project I am trying to display a rotating red square in the Android emulator. I have it working but my square is black:
Here's the code of my MainPage.xaml.cs (am using ES20 as ES10 does not have the OpenGL functions I need):
using OpenTK.Graphics.ES20;
using System;
using Xamarin.Forms;
namespace OGLMobile
{
public partial class MainPage : ContentPage
{
private float red = 1.0f;
private float green = 0.0f;
private float blue = 0.0f;
private const float halfWidth = 0.2f;
private float[] m_vertex_buffer_data = { 0.5f - halfWidth, 0.5f - halfWidth, 0.0f,
0.5f + halfWidth, 0.5f - halfWidth, 0.0f,
0.5f + halfWidth, 0.5f + halfWidth, 0.0f,
0.5f - halfWidth, 0.5f + halfWidth, 0.0f };
private string[] m_szVertexShader = null;
private string[] m_szFragmentShader = null;
private int m_nProgram = -1;
private int m_nVertexShaderHandle = -1;
private int m_nFragmentShaderHandle = -1;
private uint[] indices = { 0, 1, 2, 0, 2, 3 };
private int m_nVertexBuffer = -1;
bool m_bOGLParametersSet = false;
private void RotateSquare(float radians, float xRotationCentre, float yRotationCentre)
{
int[] nBaseIndices = { 0, 3, 6, 9 };
for (int nVertex = 0; nVertex <= 3 ; nVertex++)
{
int nIndex1 = nBaseIndices[nVertex];
int nIndex2 = nIndex1 + 1;
float offsetX = m_vertex_buffer_data[nIndex1] - xRotationCentre;
float offsetY = m_vertex_buffer_data[nIndex2] - yRotationCentre;
double xRotated = offsetX * Math.Cos(radians) - offsetY * Math.Sin(radians);
double yRotated = offsetX * Math.Sin(radians) + offsetY * Math.Cos(radians);
m_vertex_buffer_data[nIndex1] = (float)xRotated + xRotationCentre;
m_vertex_buffer_data[nIndex2] = (float)yRotated + yRotationCentre;
}
GL.BindBuffer(BufferTarget.ArrayBuffer, m_nVertexBuffer);
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, new IntPtr(m_vertex_buffer_data.Length * sizeof(float)), m_vertex_buffer_data);
}
public MainPage()
{
Title = "OpenGL";
var view = new OpenGLView { HasRenderLoop = true };
var toggle = new Switch { IsToggled = true };
m_szVertexShader = new string[1];
m_szFragmentShader = new string[1];
view.HeightRequest = 300;
view.WidthRequest = 300;
GL.Viewport(0, 0, 300, 300);
view.OnDisplay = r =>
{
if(!m_bOGLParametersSet) // Do this only on first rendering
{
CreateShader();
m_bOGLParametersSet = true;
GL.UseProgram(m_nProgram);
GL.GenBuffers(1, out m_nVertexBuffer);
GL.BindBuffer(BufferTarget.ArrayBuffer, m_nVertexBuffer);
GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(m_vertex_buffer_data.Length * sizeof(float)), m_vertex_buffer_data, BufferUsage.StaticDraw);
}
GL.ClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GL.Clear((ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit));
RotateSquare(0.0174533f, 0.5f, 0.5f);
GL.EnableVertexAttribArray(0);
GL.BindBuffer(BufferTarget.ArrayBuffer, m_nVertexBuffer);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 0, 0);
GL.DrawElements(BeginMode.Triangles, indices.Length, DrawElementsType.UnsignedInt, indices);
GL.DisableVertexAttribArray(0);
};
toggle.Toggled += (s, a) =>
{
view.HasRenderLoop = toggle.IsToggled;
};
var stack = new StackLayout
{
Padding = new Size(20, 20),
Children = { view, toggle}
};
Content = stack;
}
private void SetShaderSource()
{
m_szVertexShader[0] = "void main()" +
"{" +
"gl_Position = ftransform();" +
"}";
m_szFragmentShader[0] = "void main()" +
"{" +
"gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
"}";
}
private void CreateShader()
{
SetShaderSource();
int nVertexShaderSourceLength = m_szVertexShader[0].Length;
int nFragmentShaderLength = m_szFragmentShader[0].Length;
m_nVertexShaderHandle = GL.CreateShader(ShaderType.VertexShader);
m_nFragmentShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(m_nVertexShaderHandle, 1, m_szVertexShader, new int[] { nVertexShaderSourceLength });
GL.ShaderSource(m_nFragmentShaderHandle, 1, m_szFragmentShader, new int[] { nVertexShaderSourceLength });
GL.CompileShader(m_nVertexShaderHandle);
GL.CompileShader(m_nFragmentShaderHandle);
string szVertexShaderLog = GL.GetShaderInfoLog(m_nVertexShaderHandle);
string szFragmentShaderLog = GL.GetShaderInfoLog(m_nFragmentShaderHandle);
m_nProgram = GL.CreateProgram();
GL.AttachShader(m_nProgram, m_nVertexShaderHandle);
GL.AttachShader(m_nProgram, m_nFragmentShaderHandle);
GL.LinkProgram(m_nProgram);
}
}
}
My shaders must be incorrect, I get the same output if I replace them with junk strings, so the system must be defaulting to something.
I do have compile errors when I call GetShaderInfoLog, but my shaders are so trivial that I can't see the issue:
Thanks very much for any help, (been stuck on this for a while now. Have done plenty of OpenGL on desktop and with WebGL before, but not having any luck yet with mobile).
ADDITION: Thanks very much for you replies. I have replaced tha shaders with compliant ones, unfortunately I still have a black square:
(In the image it says mediump, I have also tried highp and lowp but I still get the same results).
I now get a different error on compilation of the vertex shader, and no error for the fragment shader - although I do have one warning for the latter:
Thank you for any further advice you may be able to offer.
These shaders do not meet any GLSL ES specification. See OpenGL ES Shading Language 1.00 Specification respectively. OpenGL ES Shading Language 3.00 Specification.
See a working shader with a vertex shader:
attribute vec3 a_position;
void main()
{
gl_Position = vec4(a_position, 1.0);
}
and fragment shader
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
I recommend to verify if the shader compiles with out errors:
GL.CompileShader(m_nVertexShaderHandle);
string infoLogFrag = GL.GetShaderInfoLog(m_nVertexShaderHandle);
if (infoLogFrag != System.String.Empty)
{
System.Console.WriteLine(infoLogFrag);
}
GL.CompileShader(m_nFragmentShaderHandle);
string infoLogVert = GL.GetShaderInfoLog(m_nFragmentShaderHandle);
if (infoLogVert != System.String.Empty)
{
System.Console.WriteLine(infoLogVert);
}
And the program links without errors:
GL.LinkProgram(m_nProgram);
string infoLogProg = GL.GetProgramInfoLog(m_nProgram );
if (infoLogProg != System.String.Empty)
{
System.Console.WriteLine(infoLogProg);
}
So, i like to make bosses, but this came along
(ref string, ref string): no suitable method found to override
And here is the code:
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace Modofmods.NPCS.Boss
{
public class BossName : ModNPC
{
public override void SetDefaults()
{
npc.name = "Vesillian";
npc.displayName = "Vesillian";
npc.aiStyle = 22; //5 is the flying AI
npc.lifeMax = 180; //boss life
npc.damage = 20; //boss damage
npc.defense = 10; //boss defense
npc.knockBackResist = 0f;
npc.width = 100;
npc.height = 100;
animationType = NPCID.DemonEye; //this boss will behavior like the DemonEye
Main.npcFrameCount[npc.type] = 2; //boss frame/animation
npc.value = Item.buyPrice(0, 40, 75, 45);
npc.npcSlots = 1f;
npc.boss = true;
npc.lavaImmune = true;
npc.noGravity = true;
npc.noTileCollide = true;
npc.soundHit = 8;
npc.soundKilled = 14;
npc.buffImmune[24] = true;
music = MusicID.Title;
npc.netAlways = true;
}
public override void AutoloadHead(ref string headTexture, ref string bossHeadTexture)
{
bossHeadTexture = "Modofmods/NPCS/Boss/BossName_Head_Boss"; //the boss head texture
}
public override void BossLoot(ref string name, ref int potionType)
{
potionType = ItemID.LesserHealingPotion; //boss drops
Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("YourSword"));
}
public override void ScaleExpertStats(int numPlayers, float bossLifeScale)
{
npc.lifeMax = (int)(npc.lifeMax * 0.579f * bossLifeScale); //boss life scale in expertmode
npc.damage = (int)(npc.damage * 0.6f); //boss damage increase in expermode
}
public override void AI()
{
npc.ai[0]++;
Player P = Main.player[npc.target];
if (npc.target < 0 || npc.target == 255 || Main.player[npc.target].dead || !Main.player[npc.target].active)
{
npc.TargetClosest(true);
}
npc.netUpdate = true;
npc.ai[1]++;
if (npc.ai[1] >= 230) // 230 is projectile fire rate
{
float Speed = 20f; //projectile speed
Vector2 vector8 = new Vector2(npc.position.X + (npc.width / 2), npc.position.Y + (npc.height / 2));
int damage = 10; //projectile damage
int type = mod.ProjectileType("ProBoss"); //put your projectile
Main.PlaySound(23, (int)npc.position.X, (int)npc.position.Y, 17);
float rotation = (float)Math.Atan2(vector8.Y - (P.position.Y + (P.height * 0.5f)), vector8.X - (P.position.X + (P.width * 0.5f)));
int num54 = Projectile.NewProjectile(vector8.X, vector8.Y, (float)((Math.Cos(rotation) * Speed) * -1), (float)((Math.Sin(rotation) * Speed) * -1), type, damage, 0f, 0);
npc.ai[1] = 0;
}
if (npc.ai[0] % 600 == 3) //Npc spown rate
{
NPC.NewNPC((int)npc.position.X, (int)npc.position.Y, mod.NPCType("NpcName")); //NPC name
}
npc.ai[1] += 0;
if (npc.life <= 70) //when the boss has less than 70 health he will do the charge attack
npc.ai[2]++; //Charge Attack
if (npc.ai[2] >= 20)
{
npc.velocity.X *= 0.98f;
npc.velocity.Y *= 0.98f;
Vector2 vector8 = new Vector2(npc.position.X + (npc.width * 0.5f), npc.position.Y + (npc.height * 0.5f));
{
float rotation = (float)Math.Atan2((vector8.Y) - (Main.player[npc.target].position.Y + (Main.player[npc.target].height * 0.5f)), (vector8.X) - (Main.player[npc.target].position.X + (Main.player[npc.target].width * 0.5f)));
npc.velocity.X = (float)(Math.Cos(rotation) * 12) * -1;
npc.velocity.Y = (float)(Math.Sin(rotation) * 12) * -1;
}
//Dust
npc.ai[0] %= (float)Math.PI * 2f;
Vector2 offset = new Vector2((float)Math.Cos(npc.ai[0]), (float)Math.Sin(npc.ai[0]));
Main.PlaySound(2, (int)npc.position.X, (int)npc.position.Y, 20);
npc.ai[2] = -300;
Color color = new Color();
Rectangle rectangle = new Rectangle((int)npc.position.X, (int)(npc.position.Y + ((npc.height - npc.width) / 2)), npc.width, npc.width);
int count = 30;
for (int i = 1; i <= count; i++)
{
int dust = Dust.NewDust(npc.position, rectangle.Width, rectangle.Height, 6, 0, 0, 100, color, 2.5f);
Main.dust[dust].noGravity = false;
}
return;
}
}
//Boss second stage texture
private const int Sphere = 50;
public override bool PreDraw(SpriteBatch spriteBatch, Color drawColor)
{
if (npc.life <= 70)
{
spriteBatch.Draw(mod.GetTexture("NPCs/Boss/boss1"), npc.Center - Main.screenPosition, null, Color.White * (70f / 255f), 0f, new Vector2(Sphere, Sphere), 3f, SpriteEffects.None, 0f);
}
return true;
}
}
}
So, I put a little too much (I guess.) But i've been reaching out to people for weeks and still cant find any answers, so my last resort is this.
And its not easy, the same thing happens with my tiles, and even my armor.
I've had a look at the mod loader's ModNPC.cs and I can't see a AutoloadHead method at all.
Looking at the code I found that the properties HeadTexture and BossHeadTexture seem to be virtual, so instead of using AutolaodHead (which, if it existed, doesn't exist anymore), you should just override BossHeadTexture:
public override string BossHeadTexture => "Modofmods/NPCS/Boss/BossName_Head_Boss";
You can see the relevant properties here.
I am trying to achieve something similar to How to increase (animate) the width of the square on both ends in Unity. How can I determine the scale by which to increase the width (of the sprite) for it to fill the whole screen width?
UPDATE
Below is the Swift code for I implemented for expanding the sprite width to take the full screen width:
func expandEnemy () {
spritePosBeforeScaleX = CGPointMake((enemy?.sprite.position.x)!, (enemy?.sprite.anchorPoint.y)!)
enemy?.sprite.anchorPoint = CGPointMake((enemy?.sprite.position.x)! / self.size.width, (enemy?.sprite.anchorPoint.y)!)
let enemyScalingAction = SKAction.scaleXTo(self.size.width / (enemy?.sprite.size.width)!, duration: 1.0)
enemy!.sprite.runAction(enemyScalingAction)
delay(0.1)
{
center = CGPointMake(enemy!.sprite.size.width / 2 - (enemy!.sprite.size.width * enemy!.sprite.anchorPoint.x), enemy!.sprite.size.height / 2 - (enemy!.sprite.size.height * enemy!.sprite.anchorPoint.y))
enemy!.sprite.physicsBody = SKPhysicsBody(rectangleOfSize: enemy!.sprite.size, center: center)
}
}
It all depends on the aspect ratio of the screen and the size of the object with the SpriteRenderer. You need to scale up the gameobject that holds the spriterenderer by a factor where you take these into consideration.
[ExecuteInEditMode]
public class SpriteToScreen : MonoBehaviour {
public float sprw = 256f;
public float sprh = 256f;
float unitspp = 100f;
public float scrw = 0f;
public float scrh = 0f;
public float aspect = 0f;
public float spr_aspect = 1f;
public float factorY = 0.017578125f;
public void Update(){
scrw = Screen.width;
scrh = Screen.height;
aspect = scrw / scrh;
unitspp = this.GetComponent<SpriteRenderer>().sprite.pixelsPerUnit;
sprw = this.GetComponent<SpriteRenderer>().sprite.bounds.size.x * unitspp;
sprh = this.GetComponent<SpriteRenderer>().sprite.bounds.size.y * unitspp;
spr_aspect = sprw / sprh;
this.transform.localScale = new Vector3( (1152f / sprh * aspect) / spr_aspect,
1152f / sprh,
1 );
}
}
You can scale Image of an UI in x-axis full screen. Just get the RectTransform then modify the sizeDelta property of it to the Screen size of the device or the size of the Canvas.
The function below can scale Unity UI Image in x, y or both x and y axis full screen. The Image to scale must be under Canvas. Assign a Sprite to the Source Image of the Image component the code below should work.
//Attach the UI Image to scacle in the Editor here
public GameObject image;
To Scale:
Scale in X-axis Full Screen in 3 seconds:
StartCoroutine(scaleToFullScreen(image, 0, 3f));
Scale in Y-axis Full Screen in 3 seconds:
StartCoroutine(scaleToFullScreen(image, 1, 3f));
Scale in X-axis AND Y-axis Full Screen in 3 seconds:
StartCoroutine(scaleToFullScreen(image, 2, 3f));
The Scale function:
bool isScaling = false;
IEnumerator scaleToFullScreen(GameObject imageToScale, int scaleType, float byTime)
{
if (isScaling)
{
yield break;
}
if (scaleType < 0 || scaleType > 2)
{
Debug.Log("Invalid ScaleType. Valid Scale Types X:0, Y:1, XandY:3");
yield break;
}
isScaling = true;
Canvas canvas = imageToScale.GetComponentInParent<Canvas>();
float x, y;
if (canvas != null)
{
x = canvas.pixelRect.width;
y = canvas.pixelRect.height;
}
else
{
x = Screen.width;
y = Screen.height;
}
RectTransform rect = imageToScale.GetComponent<RectTransform>();
if (rect == null)
{
rect = imageToScale.AddComponent<RectTransform>();
}
//Center the position of the image so that it will be resized equally
rect.anchoredPosition3D = new Vector3(0, 0, rect.anchoredPosition3D.z);
//The default Size
Vector2 originalScale = rect.sizeDelta;
//The new scale we want to scale texture to
Vector2 newScale = originalScale;
if (scaleType == 0)
{
newScale.x = x;
}
else if (scaleType == 1)
{
newScale.y = y;
}
else if (scaleType == 2)
{
newScale.x = x;
newScale.y = y;
}
float counter = 0;
while (counter < byTime)
{
counter += Time.deltaTime;
rect.sizeDelta = Vector2.Lerp(originalScale, newScale, counter / byTime);
yield return null;
}
isScaling = false;
}
I'm very new to C#, the aim here is to edit the Time of an analog Clock by dragging it's handles. https://code.msdn.microsoft.com/windowsapps/Analog-Clock-Control-0e8ffcab#content this code has inpired me. I have three simple functions MouseDown, MouseMove and MouseUp but still I can not get Drag to work. Any suggestions please ?
public partial class Form1 : Form
{
#region Construct the clock
public Point Start { get; set; }
public Point End { get; set; }
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
//Create the timer and start it
ClockTimer.Tick += ClockTimer_Tick;
ClockTimer.Enabled = true;
ClockTimer.Interval = 1;
ClockTimer.Start();
Start = p1;
End = p2;
}
#endregion
#region Update the clock
private void ClockTimer_Tick(object sender, EventArgs e)
{
Refresh();
}
private Timer ClockTimer = new Timer();
private Pen circle = new Pen(Color.Black, 2);
private Pen secondHandle = new Pen(Color.Red, 1);
private Pen minHandle = new Pen(Color.Black, 5);
private Pen hrHandle = new Pen(Color.Black, 5);
private Point p1;
private Point p2;
#endregion
#region On paint
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//Clear the graphics to the back color of the control
pe.Graphics.Clear(BackColor);
//Draw the border of the clock
pe.Graphics.DrawEllipse(circle, 0, 0, 300, 300);
//Find the radius of the control by dividing the width by 2
float radius = (300 / 2);
//Find the origin of the circle by dividing the width and height of the control
PointF origin = new PointF(300 / 2, 300 / 2);
//Draw only if ShowMajorSegments is true;
if (ShowMajorSegments)
{
//Draw the Major segments for the clock
for (float i = 0f; i != 390f; i += 30f)
{
pe.Graphics.DrawLine(Pens.White, PointOnCircle(radius - 1, i, origin), PointOnCircle(radius - 21, i, origin));
}
}
//Draw only if ShowMinorSegments is true
if (ShowMinorSegments)
{
//Draw the minor segments for the control
for (float i = 0f; i != 366f; i += 6f)
{
pe.Graphics.DrawLine(Pens.Black, PointOnCircle(radius, i, origin), PointOnCircle(radius - 10, i, origin));
}
}
//Draw only if ShowSecondHand is true
if (ShowSecondhand)
//Draw the second hand
pe.Graphics.DrawLine(secondHandle, origin, PointOnCircle(radius, DateTime.Now.Second * 6f, origin));
//Draw only if ShowMinuteHand is true
if (ShowMinuteHand)
//Draw the minute hand
pe.Graphics.DrawLine(minHandle, origin, PointOnCircle(radius * 0.75f, DateTime.Now.Minute * 6f, origin));
minHandle.StartCap = LineCap.RoundAnchor;
minHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Draw only if ShowHourHand is true
if (ShowHourHand)
//Draw the hour hand
pe.Graphics.DrawLine(hrHandle, origin, PointOnCircle(radius * 0.50f, DateTime.Now.Hour * 30f, origin));
hrHandle.StartCap = LineCap.RoundAnchor;
hrHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
#endregion
#region On size changed
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//Make sure the control is square
if (Size.Height != Size.Width)
Size = new Size(Size.Width, Size.Width);
//Redraw the control
Refresh();
}
#endregion
#region Point on circle
private PointF PointOnCircle(float radius, float angleInDegrees, PointF origin)
{
//Find the x and y using the parametric equation for a circle
float x = (float)(radius * Math.Cos((angleInDegrees - 90f) * Math.PI / 180F)) + origin.X;
float y = (float)(radius * Math.Sin((angleInDegrees - 90f) * Math.PI / 180F)) + origin.Y;
return new PointF(x, y);
}
#endregion
#region Show Minor Segments
private bool showMinorSegments = true;
public bool ShowMinorSegments
{
get
{
return showMinorSegments;
}
set
{
showMinorSegments = value;
Refresh();
}
}
#endregion
#region Show Major Segments
private bool showMajorSegments = true;
public bool ShowMajorSegments
{
get
{
return showMajorSegments;
}
set
{
showMajorSegments = value;
Refresh();
}
}
#endregion
#region Show Second Hand
private bool showSecondHand = false;
public bool ShowSecondhand
{
get
{
return showSecondHand;
}
set
{
showSecondHand = value;
Refresh();
}
}
#endregion
#region Show Minute Hand
private bool showMinuteHand = true;
public bool ShowMinuteHand
{
get
{
return showMinuteHand;
}
set
{
showMinuteHand = value;
Refresh();
}
}
#endregion
#region Show Hour Hand
private bool showHourHand = true;
public bool ShowHourHand
{
get
{
return showHourHand;
}
set
{
showHourHand = value;
Refresh();
}
}
#endregion
public float slope
{
get
{
return (((float)p2.Y - (float)p1.Y) / ((float)p2.X - (float)p1.X));
}
}
public float YIntercept
{
get
{
return p1.Y - slope * p1.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y - cushion) && temp <= (p.Y + cushion))
{
return true;
}
else
{
return false;
}
}
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(p1.X - e.Location.X, p1.Y - e.Location.Y);
deltaEnd = new Point(p2.X - e.Location.X, p2.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null)
{
p1 = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
p2 = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
}
I give a partial answer about translating a X, Y coordinate to an angle (in degree) based on a circle, where the 0° angle is located at the top.
(Scroll down for a compact solution)
Following the directions of typical GUI coordinates, the absolute 0,0 Point is located top left, positive X values stretch to the right and positive Y values stretch to the bottom.
In order to simplify the math, I use a virtual 0,0 point at the center of the circle, so all coordinates need to be translated to locals before calculation and to globals before actual drawing.
Coordinate overview (imagine the circle around 0; 0):
(0;-1)
(-1; 0) (0; 0) (1; 0)
(0; 1)
Now the task is for any coordinate (X; Y) to find the clock-wise angle between the line (0; 0) - (0; -1) and the line (0; 0) - (X; Y)
The circle can be divided into 4 quarter-circles, each covering a combination of signed (X; Y) values.
Quarter 1 contains the angle values 0° to 90° and is represented by positive X values and negative Y values.
Quarter 2 contains the angle values 90° to 180° and is represented by positive X values and positive Y values.
Quarter 3 contains the angle values 180° to 270° and is represented by negative X values and positive Y values.
Quarter 4 contains the angle values 270° to 360° and is represented by negative X values and negative Y values.
Note that for the corner cases 0°, 90°, 180°, 270°, 360° it doesn't really matter which of the two quarters they are assigned to.
The easiest way to understand such problems is to stick to the normal circle -> read: to normalize the X; Y coordinate to a length of 1. Additionally I go with positive values (it would also work without, but a bit differently in the + and - combinations):
var len = Math.Sqrt(X * X + Y * Y);
var xNorm = Math.Abs(X) / len;
var yNorm = Math.Abs(Y) / len;
Now, the reverse sine / cosine can be used to translate the normalized coordinates back into angle values (there's some redundancy in my calculation for the sake of simplicity and completeness):
var angleFromX = Math.Asin(xNorm) * 180.0 / Math.PI;
var angleFromY = Math.Asin(yNorm) * 180.0 / Math.PI;
Now lets apply the appropriate angle for each of the quarter circle areas
var resultAngle = 0.0;
if (quarter_1)
{
resultAngle = 0 + angleFromX;
// same as
resultAngle = 90 - angleFromY;
}
if (quarter_2)
{
resultAngle = 90 + angleFromY;
// same as
resultAngle = 180 - angleFromX;
}
if (quarter_3)
{
resultAngle = 180 + angleFromX;
// same as
resultAngle = 270 - angleFromY;
}
if (quarter_4)
{
resultAngle = 270 + angleFromY;
// same as
resultAngle = 360 - angleFromX;
}
Ofcourse, the quarter_1 - quarter_4 are pseudo-variables that represent the quarter selection as explained.
A more compact solution can be found by analyzing the different properties of the full solution.
var angleFromYAxis = Math.Asin(Y / Math.Sqrt(X * X + Y * Y)) * 180.0 / Math.PI;
var resultAngle = 0.0;
if (X >= 0)
{
resultAngle = 90 + angleFromYAxis;
}
else
{
resultAngle = 270 - angleFromYAxis;
}
Im playing around with the Platformer Starter Kit and so far I've added in horizontal and vertical "camera" movement and Im trying to add inn a parallaxing background. The problem is that after two background layers it stops showing the rest of them. Im very new to XNA and need a little help :). Heres a pic of the problem:
Heres the code. Please tell me if you need some more :)
Layer classes:
class Layer
{
public Texture2D[] Textures { get; private set; }
public float ScrollRate { get; private set; }
public Layer(ContentManager content, string basePath, float scrollRate)
{
// Assumes each layer only has 3 segments.
Textures = new Texture2D[3];
for (int i = 0; i < 3; ++i)
Textures[i] = content.Load<Texture2D>(basePath + "_" + i);
ScrollRate = scrollRate;
}
public void Draw(SpriteBatch spriteBatch, float cameraPosition, float cameraPositionYAxis)
{
// Assume each segment is the same width.
int segmentWidth = Textures[0].Width;
// Calculate which segments to draw and how much to offset them.
float x = cameraPosition * ScrollRate;
float y = ScrollRate;
int leftSegment = (int)Math.Floor(x / segmentWidth);
int rightSegment = leftSegment + 1;
x = (x / segmentWidth - leftSegment) * -segmentWidth;
spriteBatch.Draw(Textures[leftSegment % Textures.Length], new Vector2(x, -y), Color.White);
spriteBatch.Draw(Textures[rightSegment % Textures.Length], new Vector2(x + segmentWidth, -y), Color.White);
}
}
Heres the draw method in my Level.cs with my ScrollCamera (dont know if ScrollCamera has anything to do with it)
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
Matrix cameraTransformYAxis = Matrix.CreateTranslation(-cameraPosition, -cameraPositionYAxis, 0.0f);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp,
DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, cameraTransformYAxis);
//added this foreach loop
foreach (var layer in layers)
{
layer.Draw(spriteBatch, cameraPosition, cameraPositionYAxis);
}
DrawTiles(spriteBatch);
Player.Draw(gameTime, spriteBatch);
foreach (Enemy enemy in enemies)
{
enemy.Draw(gameTime, spriteBatch);
}
spriteBatch.End();
}
private void ScrollCamera(Viewport viewport)
{
#if ZUNE
const float ViewMargin = 0.4f;
#else
const float ViewMargin = 0.5f;
#endif
float marginWidth = viewport.Width * ViewMargin;
float marginLeft = cameraPosition + marginWidth;
float marginRight = cameraPosition + viewport.Width - marginWidth;
const float TopMargin = 0.4f;
const float BottomMargin = 0.4f;
float marginTop = cameraPositionYAxis + viewport.Height * TopMargin;
float marginBottom = cameraPositionYAxis + viewport.Height - viewport.Height * BottomMargin;
// float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
float CameraMovement = 0.0f;
if (Player.Position.X < marginLeft)
CameraMovement = Player.Position.X - marginLeft;
else if (Player.Position.X > marginRight)
CameraMovement = Player.Position.X - marginRight;
//Aktualizuj przesuwanie ekranu, ale zapobiegnij wyjściu poza mape
float maxCameraPosition = Tile.Width * Width - viewport.Width;
cameraPosition = MathHelper.Clamp(cameraPosition + CameraMovement, 0.0f, maxCameraPosition);
float cameraMovementY = 0.0f;
if (Player.Position.Y < marginTop) //above the top margin
cameraMovementY = Player.Position.Y - marginTop;
else if (Player.Position.Y > marginBottom) //below the bottom margin
cameraMovementY = Player.Position.Y - marginBottom;
float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
cameraPositionYAxis = MathHelper.Clamp(cameraPositionYAxis + cameraMovementY, 0.0f, maxCameraPositionYOffset);
}
And I think thats it. Please tell me if you need some more code :)
You want to use Linear Wrapping. There's an excellent blog post on it right here. This assumes of course that your texture tiles perfect. You just simply need to to set your linear wrapping mode, code example below:
// Use this one instead!
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
spriteBatch.Draw(texture, position, new Rectangle(-scrollX, -scrollY, texture.Width, texture.Height), Color.White);
spriteBatch.End();