This is a duplicate of another question (8778874). However, the accepted answer is not working for me, please help me find my mistake.
I have a javascript class with multiple prototype methods but for some reason I am unable to call one prototype method from another.
var Particles = function() {};
Particles.prototype.physicsStep = function() {
this.computeForces(); // error in Chrome
this.integrationStep(); // error in Chrome
}
Particles.prototype.computeForces = function() {
// do some stuff
}
Particles.prototype.integrationStep = function() {
// do some stuff
}
Chrome always throws the error "Uncaught TypeError: this.computeForces is not a function". I must be missing the point entirely. Any help would be much appreciated.
particles.js
// define particles class
var Particles = function() {
// program constants
this.dt = 0.01;
this.NUM_CIRCLES = 50;
this.DRAG_COEFF = 1;
this.SIMILARITY_COEFF = 0.05;
this.INTER_CIRCLE_ALPHA = 1000;
this.INTER_CIRCLE_GAMMA = 1;
this.BOUNDARY_ALPHA = 100;
this.BOUNDARY_GAMMA = 2;
this.MAX_FORCE = 10;
this.MASS_SCALE = 5;
this.STATE_LPF_POL = 0.01;
this.MODEL_UPDATE_INTERVAL_SECONDS = 1;
// simulation state
this.t = null;
this.positions = null;
this.old_positions = null;
this.radii = null;
this.velocities = null;
this.forces = null;
this.boundaries = {x: null, y: null};
};
//////////////////////////////////////////////////////////////////////////////////////////////
// Public Interface
//////////////////////////////////////////////////////////////////////////////////////////////
/*
physicsStep()
-------------
step forward the particle simulation.
*/
Particles.prototype.physicsStep = function() {
this.computeForces();
this.integrationStep();
}
/*
initState()
-----------
initialize physics state to all zeros.
*/
Particles.prototype.initState = function() {
this.t = 0;
this.boundaries = {x: [0, 1], y: [0, 1]};
this.positions = [];
this.old_positions = [];
this.radii = [];
this.velocities = [];
this.forces = [];
for(i = 0; i < this.NUM_CIRCLES; i++) {
this.positions.push(new THREE.Vector2(0,0));
this.old_positions.push(new THREE.Vector2(0,0));
this.radii.push(0);
this.velocities.push(new THREE.Vector2(0,0));
this.forces.push(new THREE.Vector2(0,0));
}
}
/*
initModel()
-----------
initialize model parameters to zeros.
*/
Particles.prototype.initModel = function() {
// initialize the model
this.similarities = [];
for(i = 0; i < this.NUM_CIRCLES; i++) {
this.similarities.push([]);
for(j = 0; j < this.NUM_CIRCLES; j++) {
this.similarities[i].push(0);
}
}
}
/*
updateModel()
-------------
get new parameters for the model.
currently implemented with placeholder random update.
*/
Particles.prototype.updateModel = function() {
for(i = 0; i < this.NUM_CIRCLES; i++) {
for(j = i+1; j < this.NUM_CIRCLES; j++) {
// place holder for now
this.similarities[i][j] = (1 - 2*Math.random());
}
}
}
/*
setBoundaries(xlims, ylims)
---------------------------
sets the x and y boundaries for the particle simulation.
xlims is [left, right].
yllims is [bottom, top].
*/
Particles.prototype.setBoundaries = function(xlims, ylims) {
if(xlims != null) this.boundaries.x = xlims;
if(ylims != null) this.boundaries.y = ylims;
}
/*
randomizeState()
----------------
randomizes the state of the simulation.
*/
Particles.prototype.randomizeState = function() {
this.t = 0;
for(i = 0; i < this.NUM_CIRCLES; i++) {
var xrange = this.boundaries.x[1] - this.boundaries.x[0];
var yrange = this.boundaries.y[1] - this.boundaries.y[0];
this.positions[i].x = this.boundaries.x[0] + xrange * Math.random();
this.positions[i].y = this.boundaries.y[0] + yrange * Math.random();
this.old_positions[i].x = this.positions[i].x;
this.old_positions[i].y = this.positions[i].y;
this.velocities[i].x = 0;
this.velocities[i].y = 0;
this.radii[i] = 0.1 * Math.min(xrange, yrange) * Math.random();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////////////////////////
/*
computeForces()
---------------
gets the forces for the next time step.
*/
Particles.prototype.computeForces = function() {
// per-particle forces
var alpha = this.BOUNDARY_ALPHA;
var gamma = this.BOUNDARY_GAMMA;
for(i = 0; i < this.NUM_CIRCLES; i++) {
// start at 0
this.forces[i].x = 0;
this.forces[i].y = 0;
// force exerted by boundaries
this.forces[i].add(FORCES.boundaryForce(this.positions[i], this.radii[i], this.boundaries.x, this.boundaries.y, alpha, gamma));
// drag force
this.forces[i].add(FORCES.dragForce(this.velocities[i], this.DRAG_COEFF));
}
// inter-particle forces
alpha = this.INTER_CIRCLE_ALPHA;
gamma = this.INTER_CIRCLE_GAMMA;
for(i = 0; i < this.NUM_CIRCLES; i++) {
for(j = i+1; j < this.NUM_CIRCLES; j++) {
// proximity repulsion force
var repulsion = FORCES.forceBetweenCircles(this.positions[i], this.radii[i], this.positions[j], this.radii[j], alpha, gamma);
// similarity attraction/repulsion force
var similarity = this.similarities[i][j] * this.SIMILARITY_COEFF;
repulsion.add(FORCES.similarityForce(this.positions[i], this.radii[i], this.positions[j], this.radii[j], similarity));
// add the forces to both particles
this.forces[i].add(repulsion);
this.forces[j].add(repulsion.negate());
}
}
// make sure no forces exceed maximum
for(i=0; i < this.NUM_CIRCLES; i++) {
if(this.forces[i].length() > this.MAX_FORCE) {
this.forces[i].normalize().multiplyScalar(this.MAX_FORCE);
}
}
}
/*
integrationStep()
-----------------
update based position and velocity based on forces
*/
Particles.prototype.integrationStep = function() {
for(i = 0; i < this.NUM_CIRCLES; i++) {
var mass = this.radii[i] * this.MASS_SCALE;
var a = new THREE.Vector2(this.forces[i].x / mass, this.forces[i].y / mass);
var pos = new THREE.Vector2(this.positions[i].x, this.positions[i].y);
// verlet integration
pos.multiplyScalar(2).sub(this.old_positions[i]).add(a.multiplyScalar(Math.pow(this.dt, 2)));
// lowpass filter
pos.addVectors(this.positions[i], pos.sub(this.positions[i]).multiplyScalar(1 - this.STATE_LPF_POLE));
// update state
this.velocities[i].subVectors(pos, this.old_positions[i]).divideScalar(2 * this.dt);
this.old_positions[i] = this.positions[i];
this.positions[i] = pos;
}
this.t += this.dt;
}
//////////////////////////////////////////////////////////////////////////////////////////////
render.js
// opengl variables
var camera, scene, renderer, width, height, res;
// shader variables
var uniforms;
// particles
var particles = new Particles();
var CONSTANTS = {
PI: Math.PI,
dt: 0.01,
NUM_CIRCLES: 50,
BORDER_PERCENT: 0.1
}
// initialize
init();
// kick off physics
window.setInterval(particles.physicsStep, 1000 * particles.dt);
// kick off model parameter update
//window.setInterval(updateModel, 1000 * CONSTANTS.MODEL_UPDATE_INTERVAL_SECONDS);
animate();
/*
init()
------
*/
function init() {
particles.initState();
particles.initModel();
initCameraAndScene();
initRenderer();
particles.randomizeState();
}
/*
animate()
---------
*/
function animate() {
requestAnimationFrame(animate);
render();
}
/*
render()
--------
*/
function render() {
updateUniforms();
renderer.render(scene, camera);
}
/*
initCameraAndScene()
-----------
setup scene and fullscreen quad.
*/
function initCameraAndScene() {
// initialize camer
camera = new THREE.Camera();
camera.position.z = 1;
// make a scene...
scene = new THREE.Scene();
// fullscreen quad
var geometry = new THREE.PlaneBufferGeometry(2,2);
var material = getShaderMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
/*
initRenderer()
--------------
initialize the opengl renderer element.
*/
function initRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
window.addEventListener('click', onMouseClick, false);
}
/*
onWindowResize(event)
---------------------
windows resize event handler. updates shader uniforms
as necessary.
*/
function onWindowResize(event) {
// hack intended to get rid of scrollable area
width = Math.max(0, window.innerWidth - 20);
height = Math.max(0, window.innerHeight - 20);
// end of hack
renderer.setSize(width, height);
res = width / height;
particles.setBoundaries([0, res], null);
}
/*
onMouseClick(event)
-------------------
mouseclick event handler. randomize state.
*/
function onMouseClick(event) {
particles.updateModel();
}
/*
getShaderMaterial()
---------------
returns a THREE.ShaderMaterial compiled from the
shader strings found in SHADERS.vertexShader and
SHADERS.fragmentShader.
*/
function getShaderMaterial() {
// this string holds #defined constants
var constants = "";
for(var key in CONSTANTS) {
constants = constants.concat("#define ");
constants = constants.concat(key + " " + CONSTANTS[key]);
constants = constants.concat("\n");
}
// shader variables
uniforms = {
screenwidth: {type: "f", value: window.innerWidth},
screenheight: {type: "f", value: window.innerHeight},
t: {type: "f", value: 0},
centers: {type: "v2v", value: []},
radii: {type: "fv1", value: []}
};
// make the material
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: constants + SHADERS.vertexShader,
fragmentShader: constants + SHADERS.fragmentShader
});
return material;
}
/*
updateUniforms()
----------------
sets the shader uniforms based on the current simulation state.
*/
function updateUniforms() {
uniforms.t.value = particles.t;
uniforms.screenwidth.value = width;
uniforms.screenheight.value = height;
uniforms.centers.value = particles.positions;
uniforms.radii.value = particles.radii;
}
there are some other files as well, but i think the error must be in one of these two. thank you for your help.
With your current code, you can just do this:
var p = new Particles();
p.physicsStep();
And, then inside of physicsStep()
, it will appropriately execute this.computeForces()
and this.integrationStep()
and this
will be a pointer to the p
object that was created in the first line of code above.
The value of this
is set by how a method/function is called as described here so if you are having problems with the value of this
, then the issue is likely not in the method itself, but in how it is being called.
If you want help with that part of your code, then can add that code to your question and let us take a look.
Working example: http://jsfiddle.net/jfriend00/yuxppyyf/
Yes, you are correct one problem does have to do with how you are using setInterval.
You can change this:
window.setInterval(particles.physicsStep, 1000 * particles.dt);
to this:
window.setInterval(particles.physicsStep.bind(particles), 1000 * particles.dt);
When you pass particles.physicsStep
as a function reference to another function, the particles
part of that gets lost. All that is passed is a reference to the physicsStep
method so when setInterval then calls it, it is called as an ordinary function, not as a method of your object.
This is a common mistake in Javascript and there are a couple ways to deal with that issue. I showed using .bind()
above. You can also make your own little stub function (which is essentially what .bind()
does for you) like this:
window.setInterval(function() {
particles.physicsStep();
}, 1000 * particles.dt);
This ensures that physicStep()
is called on the right object.
FYI, similar problem and answer here: How to get callback to work with "this" in class scope