How to use Polyline in Cesium to create an orbital line? - javascript

So I am trying to create a satellite tracker using Cesium.js and Satellite.js. After countless attempts, I am still unable to to use Polyline to create a line to show the orbital path of the satellite. I have managed to display a point that resembles the satellite orbiting the globe, I just need the actual line to aid the visualisation.
I have looked through the Cesium Documentation multiple times but most of the examples don't seem to work.
If anyone could help I would really appreciate it!
const viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: new Cesium.TileMapServiceImageryProvider({
url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
}),
geocoder: false, infoBox: false,
navigationHelpButton: false
});
viewer.scene.globe.enableLighting = true;
const polylines = new Cesium.PolylineCollection({show: true});
let currentTLE;
function plotSAT() {
for (let i = 0; i < listOfTLE.length; i++) {
try {
currentTLE = listOfTLE[i];
const TLErec = satellite.twoline2satrec(
currentTLE.split('\n')[0].trim(),
currentTLE.split('\n')[1].trim()
);
const totalSeconds = 60 * 60 * 6;
const timestepInSeconds = 10;
const start = Cesium.JulianDate.fromDate(new Date());
const stop = Cesium.JulianDate.addSeconds(start, totalSeconds, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.timeline.zoomTo(start, stop);
viewer.clock.multiplier = 40;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
const positionsOverTime = new Cesium.SampledPositionProperty();
let p;
let position;
for (let i = 0; i < totalSeconds; i+= timestepInSeconds) {
const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
const jsDate = Cesium.JulianDate.toDate(time);
const positionAndVelocity = satellite.propagate(TLErec, jsDate);
const gmst = satellite.gstime(jsDate);
p = satellite.eciToGeodetic(positionAndVelocity.position, gmst);
position = Cesium.Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);
positionsOverTime.addSample(time, position);
}
//Set Random Color
let randomColor = Math.floor(Math.random()*16777215).toString(16);
// Visualize the satellite with a red dot.
const satellitePoint = viewer.entities.add({
position: positionsOverTime,
point: { pixelSize: 5, color: Cesium.Color.fromCssColorString(`#${randomColor}`) },
});
const orbitalLine = polylines.add({
show: true,
id: i,
positions: positionsOverTime,
material: Cesium.Material({
fabric: {
type: 'Color',
uniforms: {
color: Cesium.Color.fromCssColorString(`#${randomColor}`)
}
},
}),
})
viewer.entities.add(polylines);
} catch(err) {
console.log(`Error Loading TLE: ${currentTLE}`);
continue;
}
}
}
plotSAT();
polylines.update();
// Wait for globe to load then zoom out
let initialized = false;
viewer.scene.globe.tileLoadProgressEvent.addEventListener(() => {
if (!initialized && viewer.scene.globe.tilesLoaded === true) {
viewer.clock.shouldAnimate = true;
initialized = true;
viewer.scene.camera.zoomOut(7000000);
document.querySelector("#loading").classList.toggle('disappear', true)
}
});
This is the specific part I need help with:
// Visualize the satellite with a red dot.
const satellitePoint = viewer.entities.add({
position: positionsOverTime,
point: { pixelSize: 5, color: Cesium.Color.fromCssColorString(`#${randomColor}`) },
});
const orbitalLine = polylines.add({
show: true,
id: i,
positions: positionsOverTime,
material: Cesium.Material({
fabric: {
type: 'Color',
uniforms: {
color: Cesium.Color.fromCssColorString(`#${randomColor}`)
}
},
}),
})
viewer.entities.add(polylines);

I managed to get this working for a single TLE by adding the positions to a polyline in the viewer entities, instead of using a Polylines Collection:
const totalSeconds = 60 * 60 * 6;
const timestepInSeconds = 10;
const start = JulianDate.fromDate(new Date());
const positions = [];
for (let i = 0; i < totalSeconds; i += timestepInSeconds) {
const time = JulianDate.addSeconds(start, i, new JulianDate());
const jsDate = JulianDate.toDate(time);
const positionAndVelocity = propagate(TLErec, jsDate);
const gmst = gstime(jsDate);
Log.debug("position and velocity: ", positionAndVelocity)
// #ts-ignore
const p = eciToGeodetic(positionAndVelocity.position, gmst);
const position = Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);
positions.push(position);
}
viewer.entities.add({
name: "orbit line",
polyline: {
positions: positions,
material: Color.RED,
width: 1,
}
});
Note: it may also be worth referring to the answer here about adding the polyline collection to the primitives instead of entities. How to display a polyline collection in Cesium?

Related

ThreeJS scene keeps accelerating as it goes on

Me and my friend have been working on a uni assignment - a simple Pacman clone - in ThreeJS (mandatory) and we've had this problem since pretty much the beginning. As the scene keeps running, it starts going faster and faster with each second. It's not like there becomes less stuff to render, everything is pretty much the same.
import * as THREE from '././../three.js-master/build/three.module.js';
function main() {
const canvas = document.querySelector('#canva');
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(window.innerWidth, window.innerHeight);
/* Camera */
const fov = 40;
const aspect = window.innerWidth / window.innerHeight;
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// I want to go for that angled look
// Angled enough to show depth, but still have everything important visible
camera.position.set(0, 65, -45); //Man setting up the camera is fun and not at all painful
camera.up.set(0, 0, 1);
camera.lookAt(0, 0, 0);
const scene = new THREE.Scene();
/* Lights */
const mainLight = new THREE.DirectionalLight(0xffffff, .85);
mainLight.position.set(0, 20, 0);
scene.add(mainLight);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
// const light = new THREE.PointLight(0xffffff, 1);
// light.position.set(30, 0, 30);
// scene.add(light);
/* Board */
const boardGeometry = new THREE.PlaneGeometry(50, 50);
const boardMaterial = new THREE.MeshToonMaterial({ color: 0xEEEEEE, side: THREE.DoubleSide });
const board = new THREE.Mesh(boardGeometry, boardMaterial);
board.rotation.x = Math.PI / 2; //The board must be placed flat on the x axis
scene.add(board);
/* Player */
const playerBox = new THREE.Box3() // Used to determine collisions
const playerGeometry = new THREE.BoxGeometry(1, 1, 1.5);
const playerMaterial = new THREE.MeshToonMaterial({ color: 0xAAAAAA });
const player = new THREE.Mesh(playerGeometry, playerMaterial);
player.geometry.computeBoundingBox(playerBox);
/* Players Axes helper */
const axesHelper = new THREE.AxesHelper(5);
player.add(axesHelper);
scene.add(player);
/* Box helper */
const playerHelper = new THREE.Box3Helper(playerBox, 0xffff00);
scene.add(playerHelper);
/* Enemy #1 */
const enemy1Box = new THREE.Box3()
const enemy1Geometry = new THREE.BoxGeometry(1, 1, 1);
const enemy1Material = new THREE.MeshToonMaterial({ color: 0x0000ff });
const enemy1 = new THREE.Mesh(enemy1Geometry, enemy1Material);
enemy1.position.x = 2
enemy1.geometry.computeBoundingBox(enemy1Box);
scene.add(enemy1);
const enemyHelper = new THREE.Box3Helper(enemy1Box, 0xffff00);
scene.add(enemyHelper);
/////////////////////
/* Cheese */
const smallCollectibleRadius = .4
const bigCollectibleRadius = .6
/* Standard cheese */
var cheeseList = []
var cheeseBoxList = []
const cheeseBox = new THREE.Box3();
const cheeseGeometry = new THREE.SphereGeometry(smallCollectibleRadius, 100, 100);
const cheeseMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, emissive: 0xffff00 });
var cheese = new THREE.Mesh(cheeseGeometry, cheeseMaterial); // Changed const to var so it could be set to null in order to get rid of after collecting
cheese.position.set(0, 0, 3)
scene.add(cheese);
cheese.geometry.computeBoundingBox(cheeseBox);
const cheeseHelper = new THREE.Box3Helper(cheeseBox, 0xffff00);
scene.add(cheeseHelper);
/* SuperCheese */
const superCheeseGeometry = new THREE.SphereGeometry(bigCollectibleRadius, 100, 100);
const superCheeseMaterial = new THREE.MeshToonMaterial({ color: 0xcb6600, emissive: 0x0000ff });
const superCheese = new THREE.Mesh(superCheeseGeometry, superCheeseMaterial);
superCheese.position.set(0, 0, 5)
scene.add(superCheese);
/* Outer wall generation */
const walls = [];
const wallGeometry = new THREE.BoxGeometry(1, 1, 1);
const wallMaterial = new THREE.MeshToonMaterial({ color: 0xAAAAAA });
//Yes I know the wall being split into blocks might not look good for now
//Please trust me, it will make more sense later
//Top
for (var i = 0; i < 49; i++) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(24.5 - i, 0.5, 24.5)
walls.push(wall);
}
//Right
for (var i = 0; i < 49; i++) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(-24.5, 0.5, 24.5 - i)
walls.push(wall);
}
//Bottom
for (var i = 0; i < 49; i++) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(-24.5 + i, 0.5, -24.5)
walls.push(wall);
}
//Left
for (var i = 0; i < 49; i++) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(24.5, 0.5, -24.5 + i)
walls.push(wall);
}
//Tried to do the four of them as a single double loop
//Might try again later
for (var i = 0; i < walls.length; i++) scene.add(walls[i]);
//////////////////////////
function makeCoins(num, cheeseList_t = cheeseList, cheeseBoxList_t = cheeseBoxList) { //I know these names break the convention.
const smallCollectibleRadius = .4
const bigCollectibleRadius = .6
for (let i = 0; i < num; i++) {
console.log("I'm a fake cheese!")
const cheeseBox = new THREE.Box3();
const cheeseGeometry = new THREE.SphereGeometry(smallCollectibleRadius, 100, 100);
const cheeseMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, emissive: 0xffff00 });
var cheese = new THREE.Mesh(cheeseGeometry, cheeseMaterial);
cheese.position.set(0, 0, (6 + i))
cheese.userData.idd = i // it's in a dict form, {"idd":i}
scene.add(cheese);
cheese.geometry.computeBoundingBox(cheeseBox);
const cheeseHelper = new THREE.Box3Helper(cheeseBox, 0xffff00);
scene.add(cheeseHelper);
cheeseList_t.push(cheese)
cheeseBoxList_t.push(cheeseBox)
}
}
makeCoins(2) // IT WORKS, HOLY COW
console.log(cheeseList[1])
function checkCollision(box) {
/* Code sucks but works and will by changed in future anyway */
var collision = playerBox.intersectsBox(box);
if (collision == true) {
return true
}
}
function collect(item) {
/* TODO: Collecting cheeses */
}
var points = 0;
function updatePoints(amount) {
points += amount
}
document.addEventListener("keypress", function (event) {
/* Additional player movement's listener that doesn't throw 2137 msgs in the console.
It would be great to make it the only one player movement's listener */
if (checkCollision(enemy1Box)) {
/**/
console.log("You bumped into enemy1!")
}
if (cheese !== null && !('consumed' in cheese.userData) && checkCollision(cheeseBox)) {
/* This will go to collect() function once finished */
console.log("Yummy cheese!")
scene.remove(cheese)
cheese.userData.consumed = true // good to know there is this userData feature
cheese = null
updatePoints(1)
}
/* For testing purposes */
var coinBox = cheeseBoxList[0]
if (checkCollision(coinBox)){
console.log("HOLY GUACAMOLE, IT WORKS") // IT REALLY DOES!
}
});
/////////////////////////////////////////////////////////////////////////////////////////////////
/* Render stuff */
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
const speed = 0.0005
const rotSpeed = 0.00005
const dir = new THREE.Vector3();
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
/* Updating boxes */
enemy1Box.copy(enemy1.geometry.boundingBox).applyMatrix4(enemy1.matrixWorld);
playerBox.copy(player.geometry.boundingBox).applyMatrix4(player.matrixWorld);
if (cheese !== null && !('consumed' in cheese.userData)) {
cheeseBox.copy(cheese.geometry.boundingBox).applyMatrix4(cheese.matrixWorld);
}
/* TEST */
var coin = cheeseList[0]
var coinBox = cheeseBoxList[0]
coinBox.copy(coin.geometry.boundingBox).applyMatrix4(coin.matrixWorld); // HOLY COW, IT SEEMS TO WORK
/* I'm afraid it'll lag everything but I can't think of better way of doing this;
this MUST be in render(), otherwise it won't update, but for loop in HERE... ugh
I will not run this. I care for my new PC. I don't want it to burn. I don't care for yours tho. Sorry not sorry, mate
IT'S NOT FINISHED YET, DON'T THINK OF RUNNING THIS
for (let i = 0; i < num; i++) {
coin = coinList[i]
coinBox = coinBoxList[i]
coinBox.copy(coin.geometry.boundingBox).applyMatrix4(coin.matrixWorld);
}
*/
document.getElementById("points").innerHTML = points
document.addEventListener("keypress", function (event) {
if (event.keyCode == 119) { //W going forward
if (checkCollision(enemy1Box)) {
console.log("Can't go any further!") // TODO: Delete in the future
} else {
player.getWorldDirection(dir);
player.position.addScaledVector(dir, speed);
}
}
if (event.keyCode == 115) { //S going backward
player.getWorldDirection(dir);
player.position.addScaledVector(dir, -speed);
}
if (event.keyCode == 97) { //A left rotation
player.rotation.y += rotSpeed
}
if (event.keyCode == 100) { //D right rotation
player.rotation.y -= rotSpeed
}
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
You’re adding keypress event listeners inside your render loop. This means that by the time 60 frames have gone by, you’ll have 60 event listeners doing the same thing. That’s why you’re seeing your scene speeding up: the more frames have passed, the more event listeners you’ve added.
Just initialize those events outside the render loop, and you’ll see the issue go away.

PixiOverlay on leaflet, cant add another image because it is already had an entry

I'm using Pixi with PixiOverlay on leaflet. I have the following jsfiddle for a dummy simulation. The objective: once you click Add Image 2 - it adds a picture of a hamster randomly on the map.
It (almost) work.
the problem:
Error message: "BaseTexture added to the cache with an id [hamster] that already had an entry"
I couldn't figure our where to put the loader and how to integrate it properly in terms of code organization: (do I need to use it only once?) what if I have other layers to add? So I assume my challenge is here:
this.loader.load((loader, resources) => {...}
Minor: how to reduce the size of the hamster :-)
my JS code (also on jsfiddle)
class Simulation
{
constructor()
{
// center of the map
var center = [1.8650, 51.2094];
// Create the map
this.map = L.map('map').setView(center, 2);
// Set up the OSM layer
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(this.map);
this.imagesLayer = new L.layerGroup();
this.imagesLayer.addTo(this.map);
}
_getRandomCoord()
{
var randLat = Math.floor(Math.random() * 90);
randLat *= Math.round(Math.random()) ? 1 : -1;
var randLon = Math.floor(Math.random() * 180);
randLon *= Math.round(Math.random()) ? 1 : -1;
return [randLat,randLon]
}
addImage2()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
let markerTexture = resources.hamster.texture
let markerLatLng = this._getRandomCoord()
let marker = new PIXI.Sprite(markerTexture)
marker.anchor.set(0.5, 1)
let pixiContainer = new PIXI.Container()
pixiContainer.addChild(marker)
let firstDraw = true
let prevZoom
let pixiOverlay = L.pixiOverlay(utils => {
let zoom = utils.getMap().getZoom()
let container = utils.getContainer()
let renderer = utils.getRenderer()
let project = utils.latLngToLayerPoint
let scale = utils.getScale()
if (firstDraw) {
let markerCoords = project(markerLatLng)
marker.x = markerCoords.x
marker.y = markerCoords.y
}
if (firstDraw || prevZoom !== zoom) {
marker.scale.set(1 / scale)
}
firstDraw = true
prevZoom = zoom
renderer.render(container)
}, pixiContainer)
this.imagesLayer.addLayer(pixiOverlay);
})
}
addTriangle()
{
console.log("Trinalge")
var polygonLatLngs = [
[51.509, -0.08],
[51.503, -0.06],
[51.51, -15.047],
[21.509, -0.08]
];
var projectedPolygon;
var triangle = new PIXI.Graphics();
var pixiContainer = new PIXI.Container();
pixiContainer.addChild(triangle);
var firstDraw = true;
var prevZoom;
var pixiOverlay = L.pixiOverlay(function(utils) {
var zoom = utils.getMap().getZoom();
var container = utils.getContainer();
var renderer = utils.getRenderer();
var project = utils.latLngToLayerPoint;
var scale = utils.getScale();
if (firstDraw) {
projectedPolygon = polygonLatLngs.map(function(coords) {return project(coords);});
}
if (firstDraw || prevZoom !== zoom) {
triangle.clear();
triangle.lineStyle(3 / scale, 0x3388ff, 1);
triangle.beginFill(0x3388ff, 0.2);
projectedPolygon.forEach(function(coords, index) {
if (index == 0) triangle.moveTo(coords.x, coords.y);
else triangle.lineTo(coords.x, coords.y);
});
triangle.endFill();
}
firstDraw = false;
prevZoom = zoom;
renderer.render(container);
}.bind(this), pixiContainer);
this.imagesLayer.addLayer(pixiOverlay)
}
removeLayer()
{
this.imagesLayer.clearLayers();
}
}
var simulation = new Simulation();
TLDR: Updated jsfiddle:
https://jsfiddle.net/gbsdfm97/
more info below:
First problem: loading resources (textures)
There was error in console because you loaded hamster image on each click:
addImage2()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
...
Better approach is to load image (resource) once at beginning and then just reuse what is loaded in memory:
constructor()
{
...
this.markerTexture = null;
this._loadPixiResources();
}
...
_loadPixiResources()
{
this.loader = new PIXI.Loader()
this.loader.add('hamster', 'https://cdn-icons-png.flaticon.com/512/196/196817.png')
this.loader.load((loader, resources) => {
this.markerTexture = resources.hamster.texture;
})
}
...
addImage2()
{
...
let marker = new PIXI.Sprite(this.markerTexture);
Second problem: size of hamsters :)
Scale was set like this:
marker.scale.set(1 / scale)
Which was too big - so changed it to:
// affects size of hamsters:
this.scaleFactor = 0.05;
...
marker.scale.set(this.scaleFactor / scale);
Scale of hamsters (not triangles!) is now updated when zoom changes - so when user uses mouse scroll wheel etc.
Third problem: too many layers in pixiOverlay
Previously on each click on Add Image 2 or Add Triangle button there was added new pixiContainer and new pixiOverlay which was added as new layer: this.imagesLayer.addLayer(pixiOverlay);
New version is a bit simplified: there is only one pixiContainer and one pixiOverlay created at beginning:
constructor()
{
...
// Create one Pixi container for pixiOverlay in which we will keep hamsters and triangles:
this.pixiContainer = new PIXI.Container();
let prevZoom;
// Create one pixiOverlay:
this.pixiOverlay = L.pixiOverlay((utils, data) => {
...
}, this.pixiContainer)
this.imagesLayer.addLayer(this.pixiOverlay);
}
this.pixiOverlay is added as one layer
then in rest of program we reuse this.pixiOverlay
also we reuse this.pixiContainer because it is returned from utils - see:
let container = utils.getContainer() // <-- this is our "this.pixiContainer"
...
container.addChild(marker)
renderer.render(container)
Bonus: Triangles
Now you can add many triangles - one per each click.
Note: triangles do not change scale - this is a difference compared to hamsters.

How to highlight particular region in horizontal way in dygraph and how to create dynamic graph

i need to highlight y value example 20 to -10 and -30 to -45 in y axis. permanently with some color with opacity 50%, how to do.,
in this example how to add external csv file to this following code. Pls Guide me
var orig_range;
window.onload = function(){ var r = [];
var arr = ["7/13/2015 0:15:45",45,"7/13/2015 0:30",5,"7/13/2015 0:45",100,"7/13/2015 1:00",95,"7/13/2015 1:15",88,"7/13/2015 1:30",78];
for (var i = 0; i < arr.length; i++) {
r.push([ new Date(arr[i]),arr[i+1]
]);
i++;
}
orig_range = [ r[0][0].valueOf(), r[r.length - 1][0].valueOf() ];
g2 = new Dygraph(
document.getElementById("div_g"),
r, {
rollPeriod: 7,
animatedZooms: true,
// errorBars: true,
width: 1000,
height: 500,
xlabel: 'date',
ylabel: 'Pressure',
}
);
var desired_range = null;};
function approach_range() {
if (!desired_range) return;
// go halfway there
var range = g2.xAxisRange();
if (Math.abs(desired_range[0] - range[0]) < 60 &&
Math.abs(desired_range[1] - range[1]) < 60) {
g2.updateOptions({dateWindow: desired_range});
// (do not set another timeout.)
} else {
var new_range;
new_range = [0.5 * (desired_range[0] + range[0]),
0.5 * (desired_range[1] + range[1])];
g2.updateOptions({dateWindow: new_range});
animate();
}
}
function animate() {
setTimeout(approach_range, 50);
}
function zoom(res) {
var w = g2.xAxisRange();
desired_range = [ w[0], w[0] + res * 1000 ];
animate();
}
function reset() {
desired_range = orig_range;
animate();
}
function pan(dir) {
var w = g2.xAxisRange();
var scale = w[1] - w[0];
var amount = scale * 0.25 * dir;
desired_range = [ w[0] + amount, w[1] + amount ];
animate();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/dygraph/1.1.0/dygraph-combined-dev.js"></script>
<div id="div_g"></div>
<div id="output"></div>
<b>Zoom:</b>
hour
day
week
month
full
<b>Pan:</b>
left
right
i'm trying to convert graph to dynamic graph data from csv file
var data = ["te1.csv"];
g2 = new Dygraph(document.getElementById("div_g"), data,
{
drawPoints: true,
showRoller: true,
labels:['date','depth'],
});
setInterval(function() {
data.push([data]);
g2.updateOptions( { 'file': data } );
}, 1000);
i have seen example but i dont know how to link my csv file with dynamic dygraph pls guide me
This example does something extremely similar to what you want: it highlights a specific range on the x-axis. To adapt it, you'd do something like this:
new Dygraph(data, div, {
underlayCallback: function (canvas, area, g) {
var bottom = g.toDomYCoord(highlight_start);
var top = g.toDomYCoord(highlight_end);
canvas.fillStyle = "rgba(255, 255, 102, 1.0)";
canvas.fillRect(area.x, top, area.w, bottom - top);
}
})

Javascript accessing dynamic object properties

I am a novice programmer working with OpenJScad written in Javascript to build 3D models.
I am trying to figure out how to structure my code so that I can access an object's instance properties that are dynamically created with user input parameters. I have a parent Gear class with the following variable...
// Gear parent class
Gear = function(numTeeth, circularPitch, pressureAngle, clearance, thickness)
{
var pitchRadius = numTeeth * circularPitch / (2 * Math.PI);
I am making several Gear sub-classes that accept user parameters, ie...
// Spur Gear
function makeSpur(params)
{
var gear = new Gear(
params.spurTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.inputBore
);
if(params.inputBore > 0)
{
var inputBore = CSG.cylinder({start: [0,0,-params.thickness2], end:
[0,0,params.thickness2], radius: params.inputBore, resolution: 16});
gear = gear.subtract(inputBore).rotateX(90);
}
return gear;
...and then dynamically generating location coordinates based on the pitchRadius property of another Gear object...
// function main
var spurGear = makeSpur(params);
spurGear = spurGear.translate([-pinionGear.pitchRadius,0,0]);
Everything renders, except when I try to access the pitchRadius property from another Gear instance. Ive read about prototypes and accessing private / public properties, but I just can't figure out how to structure the code so that I can access instance properties in function main.
Here is the full code...
include("gears.jscad");
// Here we define the user editable parameters:
function getParameterDefinitions() {
return [
{ name: 'circularPitch', caption: 'Circular pitch:', type: 'float', initial: 5 },
{ name: 'pressureAngle', caption: 'Pressure angle:', type: 'float', initial: 20 },
{ name: 'clearance', caption: 'Clearance:', type: 'float', initial: 0 },
{ name: 'thickness', caption: 'Thickness of transmission gears:', type: 'float', initial: 5 },
{ name: 'spurTeeth', caption: 'Number of spur teeth:', type: 'int', initial: 32 },
{ name: 'pinionTeeth', caption: 'Number of pinion teeth:', type: 'int', initial: 14 },
{ name: 'bore', caption: 'Radius of shaft:', type: 'float', initial: 5 }
];
}
// Main function
function main(params)
{
// declare parts
spurGear = new makeSpur(params);
pinionGear = new makePinion(params);
// assemble parts
spurGear = spurGear.translate([-pinionGear.pitchRadius, 0, -20]); // BREAKS CODE
pinionGear = pinionGear.translate([-spurGear.pitchRadius, 0, 20]); // BREAKS CODE
return [spurGear,pinionGear];
}
// Spur Gear
function makeSpur(params)
{
var gear = new involuteGear(
params.spurTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.thickness,
params.bore
);
if(params.bore > 0)
{
var bore = CSG.cylinder({start: [0,0,-params.thickness], end: [0,0,params.thickness], radius: params.bore, resolution: 16});
gear = gear.subtract(bore).rotateX(90);
}
return gear;
}
// Pinion Gear
function makePinion(params)
{
var gear = new involuteGear(
params.pinionTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.thickness,
params.bore
);
if(params.bore > 0)
{
var bore = CSG.cylinder({start: [0,0,-params.thickness], end: [0,0,params.thickness], radius: params.bore, resolution: 16});
gear = gear.subtract(bore).rotateX(90);
}
return gear;
}
// title: Gear
// author: Joost Nieuwenhuijse
// license: MIT License
/*
For gear terminology see:
http://www.astronomiainumbria.org/advanced_internet_files/meccanica/easyweb.easynet.co.uk/_chrish/geardata.htm
Algorithm based on:
http://www.cartertools.com/involute.html
circularPitch: The distance between adjacent teeth measured at the pitch circle
*/
function involuteGear(numTeeth, circularPitch, pressureAngle, clearance, thickness)
{
// default values:
if(arguments.length < 3) pressureAngle = 20;
if(arguments.length < 4) clearance = 0;
if(arguments.length < 4) thickness = 1;
var addendum = circularPitch / Math.PI;
var dedendum = addendum + clearance;
// radiuses of the 4 circles:
this.pitchRadius = numTeeth * circularPitch / (2 * Math.PI);
// this.getpitchRadius = function() {
//return pitchRadius;
//};
var baseRadius = this.pitchRadius * Math.cos(Math.PI * pressureAngle / 180);
var outerRadius = this.pitchRadius + addendum;
var rootRadius = this.pitchRadius - dedendum;
var maxtanlength = Math.sqrt(outerRadius*outerRadius - baseRadius*baseRadius);
var maxangle = maxtanlength / baseRadius;
var tl_at_pitchcircle = Math.sqrt(this.pitchRadius*this.pitchRadius - baseRadius*baseRadius);
var angle_at_pitchcircle = tl_at_pitchcircle / baseRadius;
var diffangle = angle_at_pitchcircle - Math.atan(angle_at_pitchcircle);
var angularToothWidthAtBase = Math.PI / numTeeth + 2*diffangle;
// build a single 2d tooth in the 'points' array:
var resolution = 5;
var points = [new CSG.Vector2D(0,0)];
for(var i = 0; i <= resolution; i++)
{
// first side of the tooth:
var angle = maxangle * i / resolution;
var tanlength = angle * baseRadius;
var radvector = CSG.Vector2D.fromAngle(angle);
var tanvector = radvector.normal();
var p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[i+1] = p;
// opposite side of the tooth:
radvector = CSG.Vector2D.fromAngle(angularToothWidthAtBase - angle);
tanvector = radvector.normal().negated();
p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[2 * resolution + 2 - i] = p;
}
// create the polygon and extrude into 3D:
var tooth3d = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var allteeth = new CSG();
for(var j = 0; j < numTeeth; j++)
{
var ang = j*360/numTeeth;
var rotatedtooth = tooth3d.rotateZ(ang);
allteeth = allteeth.unionForNonIntersecting(rotatedtooth);
}
// build the root circle:
points = [];
var toothAngle = 2 * Math.PI / numTeeth;
var toothCenterAngle = 0.5 * angularToothWidthAtBase;
for(var k = 0; k < numTeeth; k++)
{
var angl = toothCenterAngle + k * toothAngle;
var p1 = CSG.Vector2D.fromAngle(angl).times(rootRadius);
points.push(p1);
}
// create the polygon and extrude into 3D:
var rootcircle = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var result = rootcircle.union(allteeth);
// center at origin:
result = result.translate([0, 0, -thickness/2]);
return result;
}
I noticed that you are actually returning CSG object in the constructor, so try to use properties container described in OpenJSCAD User guide. According to the guide properties variable is intended to store metadata for the object.
This is an example from guide:
var cube = CSG.cube({radius: 1.0});
cube.properties.aCorner = new CSG.Vector3D([1, 1, 1]);
Additional comments:
You are returning different object then this in your constructor
If you will do something like this: gear = gear.rotateX(90); then you have new object
If you will use properties then metadata is cloned when you do transformation.

Draw line with arrow cap

I am trying to draw a line with arrow at the tip of the line. But i am unable to complete it. Can any one help me.
I tried adding triangle with line but unfortunately it wont work while dynamically drawing. This is what i tried along with line.
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
}
function onMouseDrag(event) {
myPath.add(event.point);
}
function onMouseUp(event) {
var myCircle = new Path.RegularPolygon(event.point, 3, 10);
myCircle.strokeColor = 'black';
myCircle.fillColor = 'white';
}
you draw line with arrow with this code ,
paper.Shape.ArrowLine = function (sx, sy, ex, ey, isDouble) {
function calcArrow(px0, py0, px, py) {
var points = [];
var l = Math.sqrt(Math.pow((px - px0), 2) + Math.pow((py - py0), 2));
points[0] = (px - ((px - px0) * Math.cos(0.5) - (py - py0) * Math.sin(0.5)) * 10 / l);
points[1] = (py - ((py - py0) * Math.cos(0.5) + (px - px0) * Math.sin(0.5)) * 10 / l);
points[2] = (px - ((px - px0) * Math.cos(0.5) + (py - py0) * Math.sin(0.5)) * 10 / l);
points[3] = (py - ((py - py0) * Math.cos(0.5) - (px - px0) * Math.sin(0.5)) * 10 / l);
return points;
}
var endPoints = calcArrow(sx, sy, ex, ey);
var startPoints = calcArrow(ex, ey, sx, sy);
var e0 = endPoints[0],
e1 = endPoints[1],
e2 = endPoints[2],
e3 = endPoints[3],
s0 = startPoints[0],
s1 = startPoints[1],
s2 = startPoints[2],
s3 = startPoints[3];
var line = new paper.Path({
segments: [
new paper.Point(sx, sy),
new paper.Point(ex, ey)
],
strokeWidth: 1
});
var arrow1 = new paper.Path({
segments: [
new paper.Point(e0, e1),
new paper.Point(ex, ey),
new paper.Point(e2, e3)
]
});
var compoundPath = new paper.CompoundPath([line, arrow1]);
if (isDouble === true) {
var arrow2 = new paper.Path({
segments: [
new paper.Point(s0, s1),
new paper.Point(sx, sy),
new paper.Point(s2, s3)
]
});
compoundPath.addChild(arrow2);
}
return compoundPath;};
use
tool.onMouseDrag = function (event) { this.item = new paper.Shape.ArrowLine(event.downPoint.x, event.downPoint.y, event.point.x, event.point.y);
this.item.removeOnDrag();}
There is an example code in paperjs refrence which draws an arrow at the end of a vector.
Have a look at: http://paperjs.org/tutorials/geometry/vector-geometry/ (scroll all the way down to the end of the page)
A simple approach is to create a group that consists of the vector itself (the line) and the arrow head. lineStart and lineEnd are the points where the line of the arrow starts and ends.
// parameters
headLength = 10;
headAngle = 150;
lineStart = new Point(200, 200);
lineEnd = new Point (250, 250);
tailLine = new Path.Line(lineStart, lineEnd);
tailVector = lineEnd - lineStart;
headLine = tailVector.normalize(headLength);
arrow = new Group([
new Path([lineStart, lineEnd]),
new Path([
lineEnd + headLine.rotate(headAngle),
lineEnd,
lineEnd + headLine.rotate(-headAngle)
])
]);
arrow.strokeColor = 'black';
And, as previously mentioned, if you want to recreate it each time then you can make the previous code a function, something like:
// parameters
var headLength = 10;
var headAngle = 150;
var arrowColor = 'black';
// the arrow
var arrow = null;
function drawArrow(start, end) {
var tailLine = new Path.Line(start, end);
var tailVector = end - start;
var headLine = tailVector.normalize(headLength);
arrow = new Group([
new Path([start, end]),
new Path([
end + headLine.rotate(headAngle),
end,
end + headLine.rotate(-headAngle)
])
]);
arrow.strokeColor = arrowColor;
}
tool.onMouseDrag = function(e) {
if (arrow) {
arrow.remove();
}
drawArrow(e.downPoint, e.point);
}
Here is a sketch of the previous code sketch.paperjs.org
Here's an example, where I extend paper.Group.
In my example I avoid redrawing the arrow on each mousedrag. I create once on mousedown and then transform the line and the arrow parts to the appropriate position/rotation on each mousedrag.
Note: I'm fairly certain the following code can be improved a lot.
Drawing via mouse events
'use strict'
/* Arrow Class extends Group */
const Arrow = paper.Group.extend({
initialize: function (args) {
paper.Group.call(this, args)
this._class = 'Arrow'
this._serializeFields = Object.assign(this._serializeFields, {
from: null,
to: null,
headSize: null
})
this.from = args.from
this.to = args.to || args.from
this.headSize = args.headSize
// #NOTE
// `paper.project.importJSON()` passes the deserialized children
// (the arrow parts) to the `Group` superclass so there's no need to
// create them again.
if (this.children.length)
return
this.addChildren([
new paper.Path({
...args,
segments: [
this.from,
this.from
]
}),
new paper.Path({
...args,
segments: [
this.from,
this.from
]
}),
new paper.Path({
...args,
segments: [
this.from,
this.from
]
})
])
this.update(this.to)
},
update: function (point) {
const angle = this.from.subtract(point).angle - 90
this.children[0].lastSegment.point = point
this.children[1].firstSegment.point = point
this.children[1].lastSegment.point = point.add(
this.headSize,
this.headSize
)
this.children[2].firstSegment.point = point
this.children[2].lastSegment.point = point.add(
-this.headSize,
this.headSize
)
this.children[1].rotate(angle, point)
this.children[2].rotate(angle, point)
return this
}
})
paper.Base.exports.Arrow = Arrow
/* Usage */
paper.setup(document.querySelector('canvas'))
const tool = new paper.Tool()
let arrow
tool.onMouseDown = e => {
arrow = new Arrow({
from: e.point,
headSize: 10,
strokeWidth: 1,
strokeColor: '#555',
strokeCap: 'round'
})
}
tool.onMouseDrag = e => {
arrow.update(e.point)
}
canvas {
display: block;
width: 100%;
height: 100%;
margin: 0;
background: #fafafa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<canvas resize="true"></canvas>
... or just draw to a static position
If you want to just draw the arrow (without using mouse events), just do the following:
const arrow = new Arrow({
from: new paper.Point(100, 100),
to: new paper.Point(200, 200),
headSize: 10,
strokeWidth: 1,
strokeColor: '#555',
strokeCap: 'round'
})

Categories