Related
I need some help with this kinda specific animation. It is a square spiral pattern that keeps going inwards until it's fully complete. I somewhat did manage to get it going but I don't know how to stop the animation properly, and I'm not sure if the math behind it is mostly efficient/correct.
Here's what I have for now:
function createSquareSpiralPath(
strokeWidth,
width,
height,
) {
const maxIterations = Math.trunc(Math.min(width, height) / 2 / strokeWidth); // ???
let path = '';
for (let i = 0; i <= maxIterations; i++) {
const step = strokeWidth * i;
const computed = [
`${step},${height - step}`,
`${step},${step}`,
`${width - step - strokeWidth},${step}`,
`${width - step - strokeWidth},${height - step - strokeWidth} `,
];
path += computed.join(' ');
}
return path.trim();
}
.spiral {
stroke-dasharray: 6130;
stroke-dashoffset: 6130;
animation: moveToTheEnd 5s linear forwards;
}
#keyframes moveToTheEnd {
to {
stroke-dashoffset: 0;
}
}
<svg viewBox="-10 -10 350 350" height="350" width="350">
<polyline class="spiral" points="
0,350 0,0 330,0 330,330 20,330 20,20 310,20 310,310 40,310 40,40 290,40 290,290 60,290 60,60 270,60 270,270 80,270 80,80 250,80 250,250 100,250 100,100 230,100 230,230 120,230 120,120 210,120 210,210 140,210 140,140 190,140 190,190 160,190 160,160 170,160 170,170"
style="fill:transparent;stroke:black;stroke-width:20" />
Sorry, your browser does not support inline SVG.
</svg>
I added the js function just to demonstrate how I'm generating the points. As you can see the animation plays exactly how I want, I just can't find a way to wrap it up properly. Also, I'm unsure if this function would generate correct points for varying width/height/strokeWidth.
I appreciate any help! Thanks in advance. :)
PS.: I could not find a mathematical term for this pattern (square-ish spiral) so I'm more than happy to learn how to call it properly.
Edit
Based on #enxaneta answers (thank you!) it seems I'm incorrectly calculating the max number of iterations. This can be seen whenever width !== height. I'll do some research on how I'm producing this value, maybe this formula isn't adequate to properly "stop" the animation without any blank space.
I guess you also need to check if your current drawing position has already reached a maximum x/y (close to you center).
The calculation for the loops iterations works fine.
Currently you're drawing 4 new points in each step.
Depending on your stroke-width you might need to stop drawing e.g after the 2. or 3. point when you're close to the center X/Y coordinates.
let spiral1 = createSquareSpiralPath(50, 500, 1000);
let spiral1_2 = createSquareSpiralPath(20, 1000, 500);
let spiral2 = createSquareSpiralPath(150, 300, 300);
function createSquareSpiralPath(strokeWidth, width, height) {
let maxIterations = Math.trunc(Math.min(width, height) / 2 / strokeWidth);
let coords = [];
//calculate max X/Y coordinates according to stroke-width
let strokeToWidthRatio = width * 1 / strokeWidth;
let strokeToHeightRatio = height * 1 / strokeWidth;
let maxX = (width - strokeWidth / strokeToWidthRatio) / 2;
let maxY = (height - strokeWidth / strokeToHeightRatio) / 2;
for (let i = 0; i <= maxIterations; i++) {
const step = strokeWidth * i;
// calculate points in iteration
let [x1, y1] = [step, (height - step)];
let [x2, y2] = [step, step];
let [x3, y3] = [(width - step - strokeWidth), step];
let [x4, y4] = [(width - step - strokeWidth), (height - step - strokeWidth)];
//stop drawing if max X/Y coordinates are reached
if (x1 <= maxX && y1 >= maxY) {
coords.push(x1, y1)
}
if (x2 <= maxX && y2 <= maxY) {
coords.push(x2, y2)
}
if (x3 >= maxX && y3 <= maxY) {
coords.push(x3, y3)
}
if (x4 >= maxX && y4 >= maxY) {
coords.push(x4, y4)
}
}
let points = coords.join(' ');
//calc pathLength from coordinates
let pathLength = 0;
for (let i = 0; i < coords.length - 2; i += 2) {
let x1 = coords[i];
let y1 = coords[i + 1];
let x2 = coords[i + 2];
let y2 = coords[i + 3];
let length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
pathLength += length;
}
//optional: render svg
renderSpiralSVG(points, pathLength, width, height, strokeWidth);
return [points, pathLength];
}
function renderSpiralSVG(points, pathLength, width, height, strokeWidth) {
const ns = "http://www.w3.org/2000/svg";
let svgTmp = document.createElementNS(ns, "svg");
svgTmp.setAttribute(
"viewBox", [-strokeWidth / 2, -strokeWidth / 2, width, height].join(" ")
);
let newPolyline = document.createElementNS(ns, "polyline");
newPolyline.classList.add("spiral");
newPolyline.setAttribute("points", points);
svgTmp.appendChild(newPolyline);
document.body.appendChild(svgTmp);
newPolyline.setAttribute(
"style",
`fill:transparent;
stroke:black;
stroke-linecap: square;
stroke-width:${strokeWidth};
stroke-dashoffset: ${pathLength};
stroke-dasharray: ${pathLength};`
);
}
svg {
border: 1px solid red;
}
svg {
display: inline-block;
height: 20vw;
}
.spiral {
stroke-width: 1;
animation: moveToTheEnd 1s linear forwards;
}
.spiral:hover {
stroke-width: 1!important;
}
#keyframes moveToTheEnd {
to {
stroke-dashoffset: 0;
}
}
<p> Hover to see spiral lines</p>
To control the animation, instead of CSS, use the Web Animations API
https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API
Wrap all in a standard Web Component <svg-spiral> with shadowDOM, so you can have multiple components on screen without any global CSS conflicts.
set a pathLenght="100" on the polygon, so you don't have to do calculations
stroke-dasharray must be written as: strokeDasharray in WAAPI
The animation triggers an onfinish function
clicking an <svg-spiral> in the SO snippet below will restart the animation
<div style="display:grid;grid:1fr/repeat(4,1fr)">
<svg-spiral></svg-spiral>
<svg-spiral stroke="rebeccapurple" width="1000" strokewidth="10"></svg-spiral>
<svg-spiral stroke="blue" duration="10000"></svg-spiral>
<svg-spiral stroke="red" width="6000" duration="1e4"></svg-spiral>
</div>
<script>
customElements.define("svg-spiral", class extends HTMLElement {
connectedCallback() {
let strokewidth = this.getAttribute("strokewidth") || 30;
let width = this.getAttribute("width") || 500; let height = this.getAttribute("height") || width;
let points = '';
for (let i = 0; i <= ~~(Math.min(width, height) / 2 / strokewidth); i++) {
const step = strokewidth * i;
points += `${step},${height - step} ${step},${step} ${width - step - strokewidth},${step} ${width - step - strokewidth},${height - step - strokewidth} `;
}
this.attachShadow({mode:"open"}).innerHTML = `<svg viewBox="-${strokewidth/2}-${strokewidth/2} ${width} ${height}"><polyline class="spiral" pathLength="100" points="${points}z"
fill="transparent" stroke-width="${strokewidth}" /></svg>`;
this.onclick = (e) => this.animate();
this.animate();
}
animate() {
let spiral = this.shadowRoot.querySelector(".spiral");
spiral.setAttribute("stroke", this.getAttribute("stroke") || "black");
let player = spiral.animate(
[{ strokeDashoffset: 100, strokeDasharray: 100, opacity: 0 },
{ strokeDashoffset: 0, strokeDasharray: 100, opacity: 1 }],
{
duration: ~~(this.getAttribute("duration") || 5000),
iterations: 1
});
player.onfinish = (e) => { spiral.setAttribute("stroke", "green") }
}
})
</script>
I want to zoom the camera in Three/Aframe so an image fits to screen.
This is the code I'm using:
this._camera = document.getElementById('camera').getAttribute('camera')
this._ratio = this._assetWidth/this._assetHeight
this._vFOV = window.THREE.Math.degToRad( this._camera?.fov || 80 )
this._height = 2 * Math.tan( this._vFOV / 2 ) * this.data.distance
this._width = this._height * this._ratio
this._zoom = this._ratio > 1 ? this._width/window.innerWidth : this._height/window.innerHeight
console.log(this._zoom, this._ratio, this._width, window.innerWidth)
I've got to the part where I need to calculate Zoom so that the object fits to the screen, i.e. if it's landscape fits to width, if it's portrait fit to height.
I thought this was the answer but it's not. that's to calculate the camera position rather than zoom value.
I'm stuck on how you work out the zoom value.
Any clues?
Fitting an object to the screen by:
changing the camera FoV
zooming the camera
repositioning the camera / object
is quite similar once you understand where the formulas came from.
We'll use this neat image (from this SO thread) as it covers all three topics:
0. What do we want to achieve
We want the object (the longer side of either its width or height) to cover the filmHeight - so it fits the screen.
1. Recalculating the FoV
In this case we do know the focalLength (camera distance from the object) and filmHeight (object width or height). We can calculate fov / 2 thanks to our friend trigonometry:
Tan (fov / 2) = (filmHeight / 2) / focalLength
=>
fov = 2 * ATan ((filmHeight / 2)) / focalLength * 180 / PI
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("fit", {
init: function() {
const plane = document.querySelector("a-plane")
const distance = this.el.object3D.position.distanceTo(plane.object3D.position)
var height = plane.getAttribute("geometry").height
var newFov = 2 * Math.atan((height / 2) / distance) * (180 / Math.PI); // in degrees
this.el.sceneEl.camera.fov = newFov
}
})
</script>
<a-scene>
<a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
<a-camera position="0 1.6 0" fit></a-camera>
</a-scene>
2. Repositioning the object / camera
Same triangle different variables. Now we want to know the focalLength:
Tan (fov / 2) = (filmHeight / 2) / focalLength
=>
focalLength = (filmHeight / 2) / Tan (fov / 2)
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("fit", {
init: function() {
const plane = document.querySelector("a-plane")
const height = plane.getAttribute("geometry").height
const fov = this.el.sceneEl.camera.fov * (Math.PI / 180);
const newDistance = Math.abs((height / 2) / Math.tan(fov / 2))
plane.object3D.position.z = -1 * newDistance;
}
})
</script>
<a-scene>
<a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
<a-camera position="0 1.6 0" fit></a-camera>
</a-scene>
3. Zooming the camera
If we know what distance should the camera be from the object for it to fill the screen - we know what is the relation between the current distance, and the new one:
zoom = currentDistance / necessaryDistance
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("fit", {
init: function() {
const plane = document.querySelector("a-plane");
const distance = this.el.object3D.position.distanceTo(plane.object3D.position);
const height = plane.getAttribute("geometry").height;
const fov = this.el.sceneEl.camera.fov * (Math.PI / 180);
const newDistance = Math.abs((height / 2) / Math.tan(fov / 2));
this.el.sceneEl.camera.zoom = distance / newDistance;
}
})
</script>
<a-scene>
<a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
<a-camera position="0 1.6 0" fit></a-camera>
</a-scene>
Needing some help... i was able to find an example of a rotating globe, that works great, i even found a way to put red circles at a point. Even better to setup a timer and everything rotates with the globe great. But if i put text on the map at the same point as the red circles it shows up at the starting point that i placed it, but as the world turns the red circle moves with the globe, but the text is frozen at the points that it was written. i am trying to get the text to rotate with the world and the red circles. think in the country of united states i want to put a number, brazil would have number when the globe rotates to china the values would still be on the countries i put it and when it rotates US and Brazil back to the front the numbers are there showing. This is what i have in code, bear with me I am still a noob when working with D3. thanks for any input...
// Initialize some variables:
var element = '#home1',
width = $("#home1").width(),
height = $("#home1").height();
var diameter = 460,
radius = diameter/2,
velocity = .001,
then = Date.now();
var features, circles;
var projection = d3.geo.orthographic()
.scale(radius - 2)
.translate([radius, radius])
.clipAngle(90);
// Save the path generator for the current projection:
var path = d3.geo.path()
.projection(projection)
.pointRadius( function(d,i) {
return radius;
});
// Define the longitude and latitude scales, which allow us to map lon/lat coordinates to pixel values:
var lambda = d3.scale.linear()
.domain([0, width])
.range([-180, 180]);
var phi = d3.scale.linear()
.domain([0, height])
.range([90, -90]);
// Create the drawing canvas:
var svg = d3.select("#home1").append("svg:svg")
.attr("width", diameter)
.attr("height", diameter);
//Create a base circle: (could use this to color oceans)
var backgroundCircle = svg.append("svg:circle")
.attr('cx', diameter / 2)
.attr('cy', diameter / 2)
.attr('r', 0)
.attr('class', 'geo-globe');
// Make a tag to group all our countries, which is useful for zoom purposes. (child elements belong to a 'group', which we can zoom all-at-once)
var world = svg.append('svg:g');
var zoomScale = 1; // default
// Create the element group to mark individual locations:
var locations = svg.append('svg:g').attr('id', 'locations');
// Having defined the projection, update the backgroundCircle radius:
backgroundCircle.attr('r', projection.scale() );
// Construct our world map based on the projection:
d3.json('world-countries.json', function(collection) {
features = world.selectAll('path')
.data(collection.features)
.enter()
.append('svg:path')
.attr('class', 'geo-path')
.attr('d', path);
// features.append('svg:title')
// .text( function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.json("data.geojson", function(collection) {
console.log("2");
cs = locations.selectAll('path')
.data(collection.features)
.enter().append('svg:path')
.datum(function(d) {return {type: "Point", coordinates: [d.geometry.coordinates[0], d.geometry.coordinates[1]]}; })
.attr('class', 'geo-node')
.attr("d", path.pointRadius(5))
.attr('d', path);
cs1 = locations.selectAll('text')
.data(collection.features)
.enter().append('svg:text')
.attr("transform", function(d) {return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.attr('d', path)
.text(function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.timer(function() {
if(offpage === 0)
{
var angle = velocity * (Date.now() - then);
projection.rotate([angle,0,0])
svg.selectAll("path").attr("d", path.projection(projection));
}
});
d3.select(window)
.on("touchmove", mousemove)
.on("touchstart", mousedown);
function mousemove() {
offpage = 0;
}
function mousedown() {
offpage=1
}
In your code, features(the world map) is a path, and cs(the city points) is a path, but cs1(the city names) is a text. In your timer you rotate the paths, which doesn't rotate the text.
My solution uses rotation degrees, instead of angle, so you'll have to adapt the formula.
d3.timer(function() {
tcounter++
rotation++
if (rotation>=360) rotation = 0
projection.rotate([rotation,0,0])
www.attr("d", path.projection(projection));
citydot.attr("d", path.projection(projection));
ctext.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")"; })
.text(function(d) {
if (((rotation + d.geometry.coordinates[0] > -90) && (rotation + d.geometry.coordinates[0] <90)) ||
((rotation + d.geometry.coordinates[0] > 270) && (rotation + d.geometry.coordinates[0] <450)))
return d.properties.city;
else return "" });
if (tcounter > 360) return true
else return false
})
I'm trying to find the visible size of a sphere in pixels, after projection to screen space. The sphere is centered at the origin with the camera looking right at it. Thus the projected sphere should be a perfect circle in two dimensions. I am aware of this 1 existing question. However, the formula given there doesn't seem to produce the result I want. It is too small by a few percent. I assume this is because it is not correctly taking perspective into account. After projecting to screen space you do not see half the sphere but significantly less, due to perspective foreshortening (you see just a cap of the sphere instead of the full hemisphere 2).
How can I derive an exact 2D bounding circle?
Indeed, with a perspective projection you need to compute the height of the sphere "horizon" from the eye / center of the camera (this "horizon" is determined by rays from the eye tangent to the sphere).
Notations:
d: distance between the eye and the center of the sphere
r: radius of the sphere
l: distance between the eye and a point on the sphere "horizon", l = sqrt(d^2 - r^2)
h: height / radius of the sphere "horizon"
theta: (half-)angle of the "horizon" cone from the eye
phi: complementary angle of theta
h / l = cos(phi)
but:
r / d = cos(phi)
so, in the end:
h = l * r / d = sqrt(d^2 - r^2) * r / d
Then once you have h, simply apply the standard formula (the one from the question you linked) to get the projected radius pr in the normalized viewport:
pr = cot(fovy / 2) * h / z
with z the distance from the eye to the plane of the sphere "horizon":
z = l * cos(theta) = sqrt(d^2 - r^2) * h / r
so:
pr = cot(fovy / 2) * r / sqrt(d^2 - r^2)
And finally, multiply pr by height / 2 to get the actual screen radius in pixels.
What follows is a small demo done with three.js. The sphere distance, radius and the vertical field of view of the camera can be changed by using respectively the n / f, m / p and s / w pairs of keys. A yellow line segment rendered in screen-space shows the result of the computation of the radius of the sphere in screen-space. This computation is done in the function computeProjectedRadius().
projected-sphere.js:
"use strict";
function computeProjectedRadius(fovy, d, r) {
var fov;
fov = fovy / 2 * Math.PI / 180.0;
//return 1.0 / Math.tan(fov) * r / d; // Wrong
return 1.0 / Math.tan(fov) * r / Math.sqrt(d * d - r * r); // Right
}
function Demo() {
this.width = 0;
this.height = 0;
this.scene = null;
this.mesh = null;
this.camera = null;
this.screenLine = null;
this.screenScene = null;
this.screenCamera = null;
this.renderer = null;
this.fovy = 60.0;
this.d = 10.0;
this.r = 1.0;
this.pr = computeProjectedRadius(this.fovy, this.d, this.r);
}
Demo.prototype.init = function() {
var aspect;
var light;
var container;
this.width = window.innerWidth;
this.height = window.innerHeight;
// World scene
aspect = this.width / this.height;
this.camera = new THREE.PerspectiveCamera(this.fovy, aspect, 0.1, 100.0);
this.scene = new THREE.Scene();
this.scene.add(THREE.AmbientLight(0x1F1F1F));
light = new THREE.DirectionalLight(0xFFFFFF);
light.position.set(1.0, 1.0, 1.0).normalize();
this.scene.add(light);
// Screen scene
this.screenCamera = new THREE.OrthographicCamera(-aspect, aspect,
-1.0, 1.0,
0.1, 100.0);
this.screenScene = new THREE.Scene();
this.updateScenes();
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.setSize(this.width, this.height);
this.renderer.domElement.style.position = "relative";
this.renderer.autoClear = false;
container = document.createElement('div');
container.appendChild(this.renderer.domElement);
document.body.appendChild(container);
}
Demo.prototype.render = function() {
this.renderer.clear();
this.renderer.setViewport(0, 0, this.width, this.height);
this.renderer.render(this.scene, this.camera);
this.renderer.render(this.screenScene, this.screenCamera);
}
Demo.prototype.updateScenes = function() {
var geometry;
this.camera.fov = this.fovy;
this.camera.updateProjectionMatrix();
if (this.mesh) {
this.scene.remove(this.mesh);
}
this.mesh = new THREE.Mesh(
new THREE.SphereGeometry(this.r, 16, 16),
new THREE.MeshLambertMaterial({
color: 0xFF0000
})
);
this.mesh.position.z = -this.d;
this.scene.add(this.mesh);
this.pr = computeProjectedRadius(this.fovy, this.d, this.r);
if (this.screenLine) {
this.screenScene.remove(this.screenLine);
}
geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0.0, 0.0, -1.0));
geometry.vertices.push(new THREE.Vector3(0.0, -this.pr, -1.0));
this.screenLine = new THREE.Line(
geometry,
new THREE.LineBasicMaterial({
color: 0xFFFF00
})
);
this.screenScene = new THREE.Scene();
this.screenScene.add(this.screenLine);
}
Demo.prototype.onKeyDown = function(event) {
console.log(event.keyCode)
switch (event.keyCode) {
case 78: // 'n'
this.d /= 1.1;
this.updateScenes();
break;
case 70: // 'f'
this.d *= 1.1;
this.updateScenes();
break;
case 77: // 'm'
this.r /= 1.1;
this.updateScenes();
break;
case 80: // 'p'
this.r *= 1.1;
this.updateScenes();
break;
case 83: // 's'
this.fovy /= 1.1;
this.updateScenes();
break;
case 87: // 'w'
this.fovy *= 1.1;
this.updateScenes();
break;
}
}
Demo.prototype.onResize = function(event) {
var aspect;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.renderer.setSize(this.width, this.height);
aspect = this.width / this.height;
this.camera.aspect = aspect;
this.camera.updateProjectionMatrix();
this.screenCamera.left = -aspect;
this.screenCamera.right = aspect;
this.screenCamera.updateProjectionMatrix();
}
function onLoad() {
var demo;
demo = new Demo();
demo.init();
function animationLoop() {
demo.render();
window.requestAnimationFrame(animationLoop);
}
function onResizeHandler(event) {
demo.onResize(event);
}
function onKeyDownHandler(event) {
demo.onKeyDown(event);
}
window.addEventListener('resize', onResizeHandler, false);
window.addEventListener('keydown', onKeyDownHandler, false);
window.requestAnimationFrame(animationLoop);
}
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Projected sphere</title>
<style>
body {
background-color: #000000;
}
</style>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r61/three.min.js"></script>
<script src="projected-sphere.js"></script>
</head>
<body onLoad="onLoad()">
<div id="container"></div>
</body>
</html>
Let the sphere have radius r and be seen at a distance d from the observer. The projection plane is at distance f from the observer.
The sphere is seen under the half angle asin(r/d), so the apparent radius is f.tan(asin(r/d)), which can be written as f . r / sqrt(d^2 - r^2). [The wrong formula being f . r / d.]
The illustrated accepted answer above is excellent, but I needed a solution without knowing the field of view, just a matrix to transform between world and screen space, so I had to adapt the solution.
Reusing some variable names from the other answer, calculate the start point of the spherical cap (the point where line h meets line d):
capOffset = cos(asin(l / d)) * r
capCenter = sphereCenter + ( sphereNormal * capOffset )
where capCenter and sphereCenter are points in world space, and sphereNormal is a normalized vector pointing along d, from the sphere center towards the camera.
Transform the point to screen space:
capCenter2 = matrix.transform(capCenter)
Add 1 (or any amount) to the x pixel coordinate:
capCenter2.x += 1
Transform it back to world space:
capCenter2 = matrix.inverse().transform(capCenter2)
Measure the distance between the original and new points in world space, and divide into the amount you added to get a scale factor:
scaleFactor = 1 / capCenter.distance(capCenter2)
Multiply that scale factor by the cap radius h to get the visible screen radius in pixels:
screenRadius = h * scaleFactor
I'm trying to rotate a point in my Canvas from a given point (center). In my MouseDown handler, I save the point where user click (oldPos), and in my MouseMove handler, I'm doing this:
private function onMouseMove(event:MouseEvent):void
{
// Where the user pointer right now
var endPoint:Point = new Point(event.localX,event.localY);
// Calculate angle in radians from the user pointer
var angle:Number = getLineAngleFromHorizontal(oldPos,endPoint);
var rad:Number = Math.PI * (angle / 180);
// Point which I want to rotate
pTop = new Point(oldPos.x,oldPos.y - 30);
var distance:Number = Point.distance(oldPos,pTop);
// Calculate the translation point from previously distance and angle
var translatePoint:Point = Point.polar(distance, rad);
// New point coordinates (in theory)
pTop.x += translatePoint.x;
pTop.y += translatePoint.y;
// Then, draw the line...
}
Where getLineAngleFromHorizontal is a function that returns the angle formed by a center and a give point:
private function getLineAngleFromHorizontal(p1:Point,p2:Point):Number
{
var RotVecOrigen:Point = new Point((p2.x-p1.x),(p2.y-p1.y));
var ModRot:Number = Math.sqrt((RotVecOrigen.x*RotVecOrigen.x)+(RotVecOrigen.y*RotVecOrigen.y));
var ret:Number;
if(((RotVecOrigen.x < 0) && (RotVecOrigen.y <= 0))||((RotVecOrigen.x >= 0) && (RotVecOrigen.y < 0)))
{
ret = Math.round((180.0*(Math.acos(RotVecOrigen.x/ModRot))/Math.PI));
}else{
ret = Math.round((180.0*(-Math.acos(RotVecOrigen.x/ModRot))/Math.PI));
}
return ret;
}
To see an example, watch the image below:
But I don't know why isn't work. I mean, pTop point isn't move where I want, and I think that my calcs are correct.
Can anybody help me? (maybe someone with Math knowledge)
I'm not entirely sure what you want to accomplish. Do you want your new point to be at an 330 degree offset from your center point?
If you want to move your point 330 degrees, use this:
function directionalDistance($start:Point, $direction:Number, $distance:Number, $zeroDegreesUp:Boolean = false):Point{
if($zeroDegreesUp) $direction = ( $direction + 270)%360;
var x:Number = Math.cos($direction * Math.PI / 180) * $distance;
var y:Number = Math.sin($direction * Math.PI / 180) * $distance;
return new Point($start.x +x, $start.y + y);
}
//
var newPoint:Point = directionalDistance(new Point(event.localX,event.localY), 330, 50, true);