I am not able to create a cloth example in matter.js - javascript

Please help me out as I am not able to run the example code from matter. When I use this code with <html>, it shows a blank screen. There is no tutorial for making a cloth simulation in google or youtube. It would be appreciated if you would help. I use sublime text for editing.
var Example = Example || {};
Example.cloth = function() {
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Body = Matter.Body,
Composites = Matter.Composites,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
Composite = Matter.Composite,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create(),
world = engine.world;
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600
}
});
Render.run(render);
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
// see cloth function defined later in this file
var cloth = Example.cloth.cloth(200, 200, 20, 12, 5, 5, false, 8);
for (var i = 0; i < 20; i++) {
cloth.bodies[i].isStatic = true;
}
Composite.add(world, [
cloth,
Bodies.circle(300, 500, 80, { isStatic: true, render: { fillStyle: '#060a19' }}),
Bodies.rectangle(500, 480, 80, 80, { isStatic: true, render: { fillStyle: '#060a19' }}),
Bodies.rectangle(400, 609, 800, 50, { isStatic: true })
]);
// add mouse control
var mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.98,
render: {
visible: false
}
}
});
Composite.add(world, mouseConstraint);
// keep the mouse in sync with rendering
render.mouse = mouse;
// fit the render viewport to the scene
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: 800, y: 600 }
});
// context for MatterTools.Demo
return {
engine: engine,
runner: runner,
render: render,
canvas: render.canvas,
stop: function() {
Matter.Render.stop(render);
Matter.Runner.stop(runner);
}
};
};
Example.cloth.title = 'Cloth';
Example.cloth.for = '>=0.14.2';
/**
* Creates a simple cloth like object.
* #method cloth
* #param {number} xx
* #param {number} yy
* #param {number} columns
* #param {number} rows
* #param {number} columnGap
* #param {number} rowGap
* #param {boolean} crossBrace
* #param {number} particleRadius
* #param {} particleOptions
* #param {} constraintOptions
* #return {composite} A new composite cloth
*/
Example.cloth.cloth = function(xx, yy, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) {
var Body = Matter.Body,
Bodies = Matter.Bodies,
Common = Matter.Common,
Composites = Matter.Composites;
var group = Body.nextGroup(true);
particleOptions = Common.extend({ inertia: Infinity, friction: 0.00001, collisionFilter: { group: group }, render: { visible: false }}, particleOptions);
constraintOptions = Common.extend({ stiffness: 0.06, render: { type: 'line', anchors: false } }, constraintOptions);
var cloth = Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y) {
return Bodies.circle(x, y, particleRadius, particleOptions);
});
Composites.mesh(cloth, columns, rows, crossBrace, constraintOptions);
cloth.label = 'Cloth Body';
return cloth;
};
if (typeof module !== 'undefined') {
module.exports = Example.cloth;
}

If you are looking to just get something on the screen, the fastest way is to use matter.js from a CDN.
You are seeing a blank screen because the example is meant to be bundled using a tool like Webpack or Parcel with other javascript files which would call the cloth() function. So all you need to do is to add Example.cloth(); at the bottom of the example code and it will execute upon loading the page.
The snippet below goes in the body tag of your HTML page which you can then open in your browser.
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js" integrity="sha512-5T245ZTH0m0RfONiFm2NF0zcYcmAuNzcGyPSQ18j8Bs5Pbfhp5HP1hosrR8XRt5M3kSRqzjNMYpm2+it/AUX/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
var Example = Example || {};
Example.cloth = function () {
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Body = Matter.Body,
Composites = Matter.Composites,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
Composite = Matter.Composite,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create(),
world = engine.world;
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600
}
});
Render.run(render);
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
// see cloth function defined later in this file
var cloth = Example.cloth.cloth(200, 200, 20, 12, 5, 5, false, 8);
for (var i = 0; i < 20; i++) {
cloth.bodies[i].isStatic = true;
}
Composite.add(world, [
cloth,
Bodies.circle(300, 500, 80, { isStatic: true, render: { fillStyle: '#060a19' } }),
Bodies.rectangle(500, 480, 80, 80, { isStatic: true, render: { fillStyle: '#060a19' } }),
Bodies.rectangle(400, 609, 800, 50, { isStatic: true })
]);
// add mouse control
var mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.98,
render: {
visible: false
}
}
});
Composite.add(world, mouseConstraint);
// keep the mouse in sync with rendering
render.mouse = mouse;
// fit the render viewport to the scene
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: 800, y: 600 }
});
// context for MatterTools.Demo
return {
engine: engine,
runner: runner,
render: render,
canvas: render.canvas,
stop: function () {
Matter.Render.stop(render);
Matter.Runner.stop(runner);
}
};
};
Example.cloth.title = 'Cloth';
Example.cloth.for = '>=0.14.2';
/**
* Creates a simple cloth like object.
* #method cloth
* #param {number} xx
* #param {number} yy
* #param {number} columns
* #param {number} rows
* #param {number} columnGap
* #param {number} rowGap
* #param {boolean} crossBrace
* #param {number} particleRadius
* #param {} particleOptions
* #param {} constraintOptions
* #return {composite} A new composite cloth
*/
Example.cloth.cloth = function (xx, yy, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) {
var Body = Matter.Body,
Bodies = Matter.Bodies,
Common = Matter.Common,
Composites = Matter.Composites;
var group = Body.nextGroup(true);
particleOptions = Common.extend({ inertia: Infinity, friction: 0.00001, collisionFilter: { group: group }, render: { visible: false } }, particleOptions);
constraintOptions = Common.extend({ stiffness: 0.06, render: { type: 'line', anchors: false } }, constraintOptions);
var cloth = Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function (x, y) {
return Bodies.circle(x, y, particleRadius, particleOptions);
});
Composites.mesh(cloth, columns, rows, crossBrace, constraintOptions);
cloth.label = 'Cloth Body';
return cloth;
};
Example.cloth();
</script>

Related

In jsxgraph, why doesn't a parabola get drawn through five points on a same plane?

In a related question I was looking for a way to draw points in 3D space so that the points will move according to slider values. Now that is working, but the conic section (a parabola in this case) I am trying to draw through these points, is not drawn.
I thought that the constructor for the element "conic" might be picky about how the given points are defined, so I ended up adding as attributes "sub-objects" that are points that can be referred to when drawing the conic.
In my code below, the constructor function PPoint creates objects that have their respective attributes pcoord, which is a geometric object of type "point" created using the native jsxgraph constructor for points. pcoord is assigned when the method "draw" is called to the draw the points I_1-I-4 and p_1.
In the last lines of the code, the parabola should be drawn by referring to the pcoords of objects I_1-I_4 and p_1, but for some reason the parabola is not drawn.
How could this be fixed? Link to jsfiddle. The code is executed without error notifications when debugging.
HTML
<div id="jxgbox" class="jxgbox" style="width:500px; height:500px">
</div>
JS
const board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-10, 10, 10, -10],
axis: true,
showCopyright: true,
showNavigation: true,
pan: false,
grid: false,
zoom: {
factorX: 1.25,
factorY: 1.25,
wheel: false
}
});
//create z axis
var zAxis = board.create('axis', [
[0, 0],
[-1, -1]
], {
ticks: {
majorHeight: 10,
drawLabels: false
}
});
//create direction of view for projections
var cam = [4, 4, 30]; // [x,y,z]
var r = 6.0;
var origin = [0, 0, 0];
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//create slider for rotating the parabola
var sRadius = board.create('slider', [
[1, -8.5],
[6, -8.5],
[-10, 0, 10]
], {
name: 'angle',
needsRegularUpdate: true
//snapWidth: 1
});
//create slider for adjusting the angular speed
var sOmega = board.create('slider', [
[1, -7.5],
[6, -7.5],
[0, 0, 10]
], {
name: 'Omega',
needsRegularUpdate: true
//snapWidth: 1,
});
//fix parameters
const g = 9.81 //gravitational acceleration
const h0 = 5 //initial height of the water surface
//define radius from the y-axis for I3 and I4
const R34 = Math.sqrt(2);
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//function creates points for drawing conic sections
function PPoint(radius, sign, namep, fixval) {
this.R = radius;
this.S = sign;
this.Namep = namep;
this.Fixval = fixval;
this.pcoord = undefined; //Cartesian coordinates of the point, stored as a point
}
//method for drawing each Point
PPoint.prototype.draw = function(pp) {
board.create('point', [function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
KK = 1 / 4 * sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.S * pp.R * Math.sin(v), K1 / 2 * pp.R * pp.R - KK + h0, pp.S * pp.R * Math.cos(v)];
//store the dynamically assigned coordinates of the point for drawing the parabola
pp.pcoord = board.create('point', [function() {
return project(c, cam);
}], {
visible: false
}); //end storing pp.coord
return project(c, cam);
}], {
fixed: this.Fixval,
name: this.Namep,
visible: true
})
}
//create and draw points
var p_1 = new PPoint(0, -1, 'p_1', 'false');
var I_1 = new PPoint(r, 1, 'I_1', 'false');
var I_2 = new PPoint(r, -1, 'I_2', 'false');
var I_3 = new PPoint(R34, 1, 'I_3', 'false');
var I_4 = new PPoint(R34, -1, 'I_4', 'false');
p_1.draw(p_1)
I_1.draw(I_1)
I_2.draw(I_2)
I_3.draw(I_3)
I_4.draw(I_4)
//draw the rotating parabola
var prbl = board.create('conic', [I_1.pcoord, I_2.pcoord, I_3.pcoord, I_4.pcoord, p_1.pcoord], {
strokeColor: '#CA7291',
strokeWidth: 2,
trace :true
});
//debugger
There are two issues with this code:
1) In PPoint.draw the reference two the JSXGraph point does not work: in each update an new JSXGraph point is created. This makes the code slow and - moreover - does not influence the initial points supplied to the conic section. I would propose to change draw to this:
PPoint.prototype.draw = function(pp) {
pp.pcoord = board.create('point', [function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
KK = 1 / 4 * sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.S * pp.R * Math.sin(v),
K1 / 2 * pp.R * pp.R - KK + h0,
pp.S * pp.R * Math.cos(v)];
return project(c, cam);
}], {
fixed: this.Fixval,
name: this.Namep,
visible: true});
2) The second problem is that JSXGraph fails to plot degenerated conics through five points and suffers in precision if the conic is close to be degenerated (there are numerical issues with general parabolas). This is the case here for the start value omega = 0.
Here is a working example: https://jsfiddle.net/L2d4zt8q/

OpenLayers 4 ruler

I'm refactoring the whole code of the app with modules and I'm having some problem with the Ruler function of OpenLayers 4 now.
In the previous code was working OK, but now when I double click on the map to stop the current measure and start a new one, the only thing left in the screen is the popup, the line (the line of what I was measuring before) is removed.
Here my code:
const initVector = (mapView) => {
/**
* vector layers sources
*/
$('.form-inline').click(function () {
addRuler()
alert("initialize")
})
let Rsource = new ol.source.Vector()
let rulerLayer = new ol.layer.Vector({
source: Rsource,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.7)',
width: 3
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: 'rgba(0, 0, 0, 0.7)'
})
})
})
})
/*
* ruler tool handler
*/
/**
* global vars
*/
// this style is done by javascript to bypass the override rule that enter in conflict with FRA
$('.markers button').css({'margin': '5px 7px', 'padding': '0 10px'})
$('#markloc').css('margin-left', '5px')
// these var are for the creation of the text and all the related element during the measure
let sketch
let helpTooltipElement
let helpTooltip
let measureTooltipElement
let measureTooltip
let ruler
/*
* pointer handler
*/
const pointerMoveHandler = (evt) => {
/*
* if the mouse is dragging the map return
*/
if (evt.dragging) {
return
}
/**
* default message to display
*/
let helpMsg = 'Click to start drawing'
/**
* check the message if you are measuring
*/
if (sketch) {
helpMsg = 'Click to continue drawing the line or double click to stop.'
}
/**
* attach to the tooltip the correct message
* set the position near the mouse cursor
* display the tooltip
*/
helpTooltipElement.innerHTML = helpMsg
helpTooltip.setPosition(evt.coordinate)
helpTooltipElement.classList.remove('hidden')
}
/**
* display the actual measured length
*/
function formatLength (line) {
const length = ol.Sphere.getLength(line)
let output
if (length > 100) {
output = `${Math.round(length / 1000 * 100) / 100} km`
}
else {
output = `${Math.round(length * 100) / 100} m`
}
return output
}
/**
* create a new tooltip
*/
function createHelpTooltip () {
helpTooltipElement = document.createElement('div')
helpTooltipElement.className = 'tooltip hidden'
helpTooltip = new ol.Overlay({
element: helpTooltipElement,
offset: [15, 0],
positioning: 'center-left'
})
mapView.addOverlay(helpTooltip)
}
/**
* Creates a new measure tooltip
*/
function createMeasureTooltip () {
measureTooltipElement = document.createElement('div')
measureTooltipElement.className = 'tooltip tooltip-measure'
measureTooltip = new ol.Overlay({
element: measureTooltipElement,
offset: [0, -15],
positioning: 'bottom-center'
})
mapView.addOverlay(measureTooltip)
}
/**
* add the ruler when you click on the button
*/
function addRuler () {
/**
* add a selected class to the ruler button to make it visible that it's in use
*/
$('#tlruler').addClass('custbtnsel')
/**
* styling ruler
*/
ruler = new ol.interaction.Draw({
source: Rsource,
type: 'LineString',
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.5)',
lineDash: [10, 10],
width: 2
}),
image: new ol.style.Circle({
radius: 5,
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.7)'
})
})
})
})
/**
* call the pointerMoveHandler to create the element on the screen when the ruler it's in use
*/
mapView.on('pointermove', pointerMoveHandler)
/**
* mouseout event listener to hidden the popup
*/
mapView.getViewport().addEventListener('mouseout', () => {
helpTooltipElement.classList.add('hidden')
})
/**
* add the ruler interaction to the map
*/
mapView.addInteraction(ruler)
/**
* create the tooltip
*/
createMeasureTooltip()
createHelpTooltip()
let listener
/**
* drawstart event
*/
ruler.on('drawstart', function (evt) {
// set sketch
sketch = evt.feature
// tooltip coordinate
let tooltipCoord = evt.coordinate
/**
* sketch event listener on change
* called during a mouse move
*/
listener = sketch.getGeometry().on('change', function (evt) {
let geom = evt.target
/**
* as we don't use polygon we check justfor line
* get last position of the cursor and the length
*/
let output
// OL 5 CODE
// if (geom instanceof LineString)
if (geom instanceof ol.geom.LineString) {
output = formatLength(geom)
tooltipCoord = geom.getLastCoordinate()
}
/**
* append to the tooltip the measure
* set the position of the tooltip to the last cursor coord
*/
measureTooltipElement.innerHTML = output
measureTooltip.setPosition(tooltipCoord)
})
}, this)
/**
* drawend event
*/
ruler.on('drawend', () => {
/**
* create the static tooltip with the last measure
*/
console.log('drawend')
measureTooltipElement.className = 'tooltip tooltip-static'
measureTooltip.setOffset([0, -7])
/**
* set sketch and the tooltip element to null
*/
sketch = null
measureTooltipElement = null
/**
* set sketch and the tooltip element to null
*/
createMeasureTooltip()
// OL 5 code
// unByKey(listener);
ol.Observable.unByKey(listener)
}, this)
/**
* end addRuler function
*/
}
}
const mapLayer = new ol.layer.Tile({
source: new ol.source.OSM()
});
let map = new ol.Map({
layers: [mapLayer],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 10
})
})
initVector(map)
the link to the pen so you can test it
https://codepen.io/sebalaini/pen/OrRELp?editors=0010
and here how should work:
https://openlayers.org/en/latest/examples/measure.html
Notice that I created a function to initialise the ruler to simulate the module, as my map is created inside a module and the ruler in another, in the map module then I import that function and I initialise it with the map variable
Thanks to #Mike that answer me in Gis.stackexchange.
In initVector after creating rulerLayer you need to add it to the map map.addLayer(rulerLayer);
A very small error.

Javascript canvas hover over a tile

I have a canvas which I am drawing from certain tiles (new Image()), lets say there are for example 10x10 tiles in my canvas.
Every tile has its own data I want to display "popup" with information about this tile everytime user hovers over the tile with mouse, it should be following the mouse until user leaves the tile.
Example of what I want to achieve would be google maps, but it should be following the mouse as I move the mouse over the tile:
https://i.gyazo.com/d32cd5869ae9b2e0d9a7053729e2d2aa.mp4
You will need three things to achieve this:
A way to draw the background image on your canvas
A way to track mouse position over the canvas
A list of "zones" and a way to determine which zone is "triggered"
Here is an implementation of the above points:
var GridOverlay = /** #class */ (function () {
/**
* Creates an instance of GridOverlay.
* #param {string} imageSrc
* #param {{
* position: IPoint,
* size: IPoint,
* message: string
* }[]} [zones=[]]
* #memberof GridOverlay
*/
function GridOverlay(imageSrc, zones) {
if (zones === void 0) { zones = []; }
var _this = this;
this.zones = zones;
/**
* The last registered position of the cursor
*
* #type {{ x: number, y: number }}
* #memberof GridOverlay
*/
this.mousePosition = { x: 0, y: 0 };
//Create an image element
this.img = document.createElement("img");
//Create a canvas element
this.canvas = document.createElement("canvas");
//When the image is loaded
this.img.onload = function () {
//Scale canvas to image
_this.canvas.width = _this.img.naturalWidth;
_this.canvas.height = _this.img.naturalHeight;
//Draw on canvas
_this.draw();
};
//Set the "src" attribute to begin loading
this.img.src = imageSrc;
//Listen for "mousemove" on our canvas
this.canvas.onmousemove = this.mouseMove.bind(this);
}
/**
* Registers the current position of the cursor over the canvas, saves the coordinates and calls "draw"
*
* #param {MouseEvent} evt
* #memberof GridOverlay
*/
GridOverlay.prototype.mouseMove = function (evt) {
this.mousePosition.x = evt.offsetX;
this.mousePosition.y = evt.offsetY;
this.draw();
};
/**
* Clears and redraws the canvas with the latest data
*
* #memberof GridOverlay
*/
GridOverlay.prototype.draw = function () {
//Get drawing context
var ctx = this.canvas.getContext("2d");
//Clear canvas
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
//Draw background
ctx.drawImage(this.img, 0, 0);
//Loop through zones
for (var zoneIndex = 0; zoneIndex < this.zones.length; zoneIndex++) {
var zone = this.zones[zoneIndex];
//Check for cursor in zone
if (zone.position.x <= this.mousePosition.x &&
zone.position.x + zone.size.x >= this.mousePosition.x &&
zone.position.y <= this.mousePosition.y &&
zone.position.y + zone.size.y >= this.mousePosition.y) {
//Display zone message on cursor position
ctx.fillText(zone.message, this.mousePosition.x, this.mousePosition.y);
//Break so that we only show a single message
break;
}
}
return this;
};
return GridOverlay;
}());
//TEST
var grid = new GridOverlay("https://placeholdit.imgix.net/~text?txtsize=60&txt=1&w=500&h=500", [
{ message: "Zone 1", position: { x: 10, y: 10 }, size: { x: 100, y: 100 } },
{ message: "Zone 2", position: { x: 80, y: 80 }, size: { x: 100, y: 100 } },
]);
document.body.appendChild(grid.canvas);

matter.js mouse click on body

I have some code like the following for the matter.js library:
// create two boxes and a ground
var boxA = Bodies.rectangle(400, 200, 80, 80);
var boxB = Bodies.rectangle(450, 50, 80, 80);
var ground = Bodies.rectangle(400, 610, 810, 60, { isStatic: true });
// add all of the bodies to the world
World.add(engine.world, [boxA, boxB, ground]);
Events.on(engine, 'tick', function(event) {
if (mouseConstraint.mouse.button == 0){
alert("what is clicked?");
}
});
Is there a way I can tell if boxA or boxB has been clicked with the mouse in the event handler?
To elaborate on this answer, here's a runnable example of using mouseConstraint.body in your event handler to determine which body is being clicked:
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
});
const bodies = [
Matter.Bodies.rectangle(
400, 310, 810, 60, {isStatic: true, angle: 0.0}
),
...[...Array(100)].map(() =>
Matter.Bodies.rectangle(
Math.random() * 400, // x
Math.random() * 100, // y
Math.random() * 50 + 10, // w
Math.random() * 50 + 10, // h
{angle: Math.random() * (Math.PI * 2)},
)
),
];
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.body}
);
const runner = Matter.Runner.create();
Matter.Events.on(runner, "tick", event => {
if (mouseConstraint.body) {
Matter.Composite.remove(engine.world, mouseConstraint.body);
}
});
// also possible, testing the condition on mousedown only:
//Matter.Events.on(mouseConstraint, "mousedown", () => {
// if (mouseConstraint.body) {
// Matter.Composite.remove(engine.world, mouseConstraint.body);
// }
//});
Matter.Composite.add(engine.world, [...bodies, mouseConstraint]);
Matter.Runner.start(runner, engine);
Matter.Render.run(render);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.js"></script>
You can also use Matter.Query.point and pass in the mouse X and Y position on click to obtain an array of bodies at that point.
mouseConstraint.body contains the body that was clicked.

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.

Categories