So I am creating a simulation of a bouncing ball, and the user can place lines the ball can collide with on the canvas by dragging from one point to another. There are essentially four lines that can be created:
So the object that stores a line is defined as such:
export interface pathSection {
xfrom: number;
yfrom: number;
xto: number;
yto: number;
length: number;
}
The first and third lines in the image for example dont give the same value from
Math.atan2(yto - yfrom, xto - from);
So given the (relative) complexity of the surfaces, I need to find the angle between a moving object and that surface at the point of collision:
The ball strikes the surface at an angle a, which is what I want!
However I am having trouble finding the angle between the two vectors. This is what I understood would work:
var dx = this.path[index_for_path_section].xfrom - this.path[index_for_path_section].xto;
var dy = this.path[index_for_path_section].yfrom - this.path[index_for_path_section].yto;
var posX = this.particle.pos.x;
var posY = this.particle.pos.y;
var posNextX = posX + this.particle.v.x;
var posNextY = posY + this.particle.v.y;
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(posNextY - posY, posNextX - posX);
var angleBetween = angleOfRamp - angleOfvelocity;
This is then used to calculate the speed of the object after the collision:
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
However the angle calculated is around -4.5 Pi, about -90 degrees for the object directly down and the surface at what looks to be around 45-60 degrees…
The red arrow shows the path of the object moving through the surface - the white dots show where a collision has been detected between the surface and the object.
Any help on how to get the correct and usable angle between the two velocity and the line would be appreciated!
Note I have tried utilizing this solution, but have struggled to adapt it to my own work.
So it took me some time, and I am not 100% sure still of why it works because I think im finding the JavaScript angles system a bit tricky, but:
var dx = this.path[collided].xfrom - this.path[collided].xto;
var dy = this.path[collided].yfrom - this.path[collided].yto;
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(this.particle.v.y, this.particle.v.x);
var angleBetween = angleOfRamp * 2 - angleOfvelocity; // not sure why :)
if (angleBetween < 0) { angleBetween += 2*Math.PI; } // not sure why :)
const restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
Thanks to all who looked :)
I'm worked with Three.JS before, but not on meshes. I think I am approaching my problem the right way, but I'm not sure.
The Goal
I'm trying to make a 3D blobby object that has specific verticies. The direction of the verticies are fixed, but their radius from center varies. You can imagine it sort of like an audio equalizer, except radial and in 3D.
I'm open to scrapping this approach and taking a totally different one if there's some easier way to do this.
Current Progress
I took this example and cleaned/modified it to my needs. Here's the HTML and JavaScript:
HTML (disco-ball.html)
<!DOCTYPE html>
<html>
<head>
<title>Disco Ball</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/ConvexGeometry.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style type='text/css'>
/* set margin to 0 and overflow to hidden, to go fullscreen */
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="Stats-output"></div>
<div id="WebGL-output"></div>
<script type="text/javascript" src="01-app.js"></script>
</body>
</html>
And the JavaScript (01-app.js):
window.onload = init;
const PARAMS = {
SHOW_SURFACE : true,
SHOW_POINTS : true,
SHOW_WIREFRAME : true,
SHOW_STATS : true
};
// once everything is loaded, we run our Three.js stuff.
function init() {
var renderParams = {
webGLRenderer : createWebGLRenderer(),
step : 0,
rotationSpeed : 0.007,
scene : new THREE.Scene(),
camera : createCamera(),
};
// Create the actual points.
var points = getPoints(
100, // Number of points (approximate)
10, // Unweighted radius
// Radius weights for a few points. This is a multiplier.
[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]
);
if (PARAMS.SHOW_STATS) {
renderParams.stats = initStats();
}
if (PARAMS.SHOW_SURFACE) {
renderParams.surface = getHullMesh(points);
renderParams.scene.add(renderParams.surface);
}
if (PARAMS.SHOW_POINTS) {
renderParams.sphereGroup = getSphereGroup(points);
renderParams.scene.add(sphereGroup);
}
render(renderParams);
}
function render(params) {
if (params.stats) {
params.stats.update();
}
if (params.sphereGroup) {
params.sphereGroup.rotation.y = params.step;
}
params.step += params.rotationSpeed;
if (params.surface) {
params.surface.rotation.y = params.step;
}
// render using requestAnimationFrame
requestAnimationFrame(function () {render(params)});
params.webGLRenderer.render(params.scene, params.camera);
}
// ******************************************************************
// Helper functions
// ******************************************************************
function getPoints (count, baseRadius, weightMap) {
// Because this is deterministic, we can pass in a weight map to adjust
// the radii.
var points = distributePoints(count,baseRadius,weightMap);
points.forEach((d,i) => {
points[i] = new THREE.Vector3(d[0],d[1],d[2]);
});
return points;
}
// A deterministic function for (approximately) evenly distributing n points
// over a sphere.
function distributePoints (count, radius, weightMap) {
// I'm not sure why I need this...
count *= 100;
var points = [];
var area = 4 * Math.PI * Math.pow(radius,2) / count;
var dist = Math.sqrt(area);
var Mtheta = Math.round(Math.PI / dist);
var distTheta = Math.PI / Mtheta
var distPhi = area / distTheta;
for (var m = 0; m < Mtheta; m++) {
let theta = (Math.PI * (m + 0.5)) / Mtheta;
let Mphi = Math.round((2 * Math.PI * Math.sin(theta)) / distPhi);
for (var n = 0; n < Mphi; n++) {
let phi = ((2 * Math.PI * n) / Mphi);
// Use the default radius, times any multiplier passed in through the
// weightMap. If no multiplier is present, use 1 to leave it
// unchanged.
points.push(createPoint(radius * (weightMap[points.length] || 1),theta,phi));
}
}
return points;
}
function createPoint (radius, theta, phi) {
var x = radius * Math.sin(theta) * Math.cos(phi);
var y = radius * Math.sin(theta) * Math.sin(phi);
var z = radius * Math.cos(theta);
return [Math.round(x), Math.round(y), Math.round(z)];
}
function createWebGLRenderer () {
// create a render and set the size
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
// add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
return webGLRenderer;
}
function createCamera () {
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// position and point the camera to the center of the scene
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(0, 0, 0));
return camera;
}
function getSphereGroup (points) {
sphereGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({color: 0xFF0000, transparent: false});
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.2);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
sphereGroup.add(spMesh);
});
return sphereGroup;
}
function getHullMesh (points) {
// use the same points to create a convexgeometry
var surfaceGeometry = new THREE.ConvexGeometry(points);
var surface = createMesh(surfaceGeometry);
return surface;
}
function createMesh(geom) {
// assign two materials
var meshMaterial = new THREE.MeshBasicMaterial({color: 0x666666, transparent: true, opacity: 0.25});
meshMaterial.side = THREE.DoubleSide;
var wireFrameMat = new THREE.MeshBasicMaterial({color: 0x0000ff});
wireFrameMat.wireframe = PARAMS.SHOW_WIREFRAME;
// create a multimaterial
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);
return mesh;
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
What I'm Missing
You can see that there are two points on the "ball" for which I've doubled the radius (big spikes). Of course, since I'm using a ConvexGeometry, the shape is convex... so a number of the points are hidden. What kind of ... non-convex geometry can I use to make those points no longer be hidden?
I would like to subdivide the mesh a bit so it's not simply vertex-to-vertex, but a bit smoother. How can I do that (the spikes less spikey and more blobby)?
I'd like to modify the mesh so different points spike different amounts every few seconds (I have some data arrays that describe how much). How do I modify the geometry after its been made? Ideally with some kind of tweening, but I can do without of that's extremely hard =)
Thanks!
Smooth and animate a mesh.
Three provides a huge range of options. These are just suggestions, your best bet is to read the Three documentation start point and find what suits you.
A mesh is just a set of 3D points and an array of indexes describing each triangle. Once you have built the mesh you only need to update the verts and let Three update the shader attributes, and the mesh normals
Your questions
Q1. Use Three.Geometry for the mesh.
Q2. As you are building the mesh you can use the curve helpers eg Three.CubicBezierCurve3 or Three.QuadraticBezierCurve3 or maybe your best option Three.SplineCurve
Another option is to use a modifier and create the simple mesh and then let Three subdivide the mesh for you. eg three example webgl modifier subdivision
Though not the fastest solution, if the vert count is low it will do this each frame without any loss of frame rate.
Q3. Using Three.Geometry you can can set the mesh morph targets, an array of vertices.
Another option is to use a modifier, eg three example webgl modifier subdivision
Or you can modify the vertices directly each frame.
for ( var i = 0, l = geometry.vertices.length; i < l; i ++ ) {
geometry.vertices[ i ].x = ?;
geometry.vertices[ i ].y = ?;
geometry.vertices[ i ].z = ?;
}
mesh.geometry.verticesNeedUpdate = true;
How you do it?
There are a zillion other ways to do this. Which is the best will depend on the load and amount of complexity you want to create. Spend some time and read the doc's, and experiment.
What I would do! maybe?
I am not too sure what you are trying to achieve but the following is a way of getting some life into the animation rather than the overdone curves that seem so ubiquitous these days.
So if the vert count is not too high I would use a Three.BufferGeometry and modify the verts each frame. Rather than use curves I would weight subdivision verts to follow a polynomial curve f(x) = x^2/(x^2 + (1-x)^2) where x is the normalized distance between two control verts (note don't use x=0.5 rather subdivide the mesh in > 2 times)
EG the two control points and two smoothing verts
// two control points
const p1 = {x,y,z};
const p2 = {x,y,z};
// two weighted points
// dx,dy,dz are deltas
// w is the weighted position s-curve
// wa, and wd are acceleration and drag coefficients. Try to keep their sum < 1
const pw1 = {x, y, z, dx, dy, dz, w : 1/3, wa : 0.1,wd : 0.7};
const pw2 = {x, y, z, dx, dy, dz, w : 2/3, wa : 0.1,wd : 0.7};
// Compute w
pw1.w = Math.pow(pw1.w,2) / ( Math.pow(pw1.w,2) + Math.pow(1 - pw1.w,2));
pw2.w = Math.pow(pw2.w,2) / ( Math.pow(pw2.w,2) + Math.pow(1 - pw2.w,2));
Then for each weighted point you can find the new delta and update the position
// do for x,y,z
x = (p2.x - p1.x); // these points are updated every frame
// get the new pw1 vert target position
x = p1.x + x * w;
// get new delta
pw1.dx += (x - pw1.x) * pw1.wa; // set delta
pw1.dx *= pw1.wd;
// set new position
pw1.x += pw1.dx;
Do for all weighted points then set geometry.vertices
The wa,wd coefficients will change the behaviour of the smoothing, you will have to play with these values to suit your own taste. Must be 0 <= (wa,wd) < 1 and the sum should be wa + wd < 1. High sumed values will result in oscillations, too high and the oscillations will be uncontrolled.
I'd like to help the user to input an orientation for a segment with OpenLayers.
I have that form where user can input the bearing for a point, but I would like to help him by :
start drawing the first vertice of a segment on the map when the user clicks on a button, (that first vertice being a known point)
then the user just has to click for the second vertice, and bearing is computed automatically.
See the fiddle here or SO snippet below.
I'm almost done : I can compute the bearing when a segment is drawn. But there's an exception at the very end of the script : I can't get OL to draw automatically the first point of my segment.
Thank you to anyone who can help.
<script src="http://openlayers.org/api/OpenLayers.js"></script>
<body>
<div id="map" style="height: 500px"></div>
</body>
<script>
var CONSTANTS = {
MAP_FROM_PROJECTION: new OpenLayers.Projection("EPSG:4326"), // Transform from WGS 1984
MAP_TO_PROJECTION: new OpenLayers.Projection("EPSG:900913") // to Spherical Mercator Projection
};
function radians(n) {
return n * (Math.PI / 180);
}
function degrees(n) {
return n * (180 / Math.PI);
}
function computeBearing(startLat, startLong, endLat, endLong) {
startLat = radians(startLat);
startLong = radians(startLong);
endLat = radians(endLat);
endLong = radians(endLong);
var dLong = endLong - startLong;
var dPhi = Math.log(Math.tan(endLat / 2.0 + Math.PI / 4.0) / Math.tan(startLat / 2.0 + Math.PI / 4.0));
if (Math.abs(dLong) > Math.PI) {
if (dLong > 0.0) dLong = -(2.0 * Math.PI - dLong);
else dLong = (2.0 * Math.PI + dLong);
}
return (degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
}
map = new OpenLayers.Map("map");
map.addLayer(new OpenLayers.Layer.OSM());
map.setCenter(new OpenLayers.LonLat(3, 47).transform(CONSTANTS.MAP_FROM_PROJECTION, CONSTANTS.MAP_TO_PROJECTION), 6);
var lineLayer = new OpenLayers.Layer.Vector("Line Layer");
map.addLayers([lineLayer]);
var lineControl = new OpenLayers.Control.DrawFeature(lineLayer, OpenLayers.Handler.Path, {
handlerOptions: {
maxVertices: 2,
freehandMode: function(evt) {
return false;
}
},
featureAdded: function(feature) {
var drawnLinePoints = feature.geometry.getVertices();
var lonlat1 = drawnLinePoints[0].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var lonlat2 = drawnLinePoints[1].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var bearingValue = computeBearing(lonlat1.y, lonlat1.x, lonlat2.y, lonlat2.x);
console.log(bearingValue);
}
});
map.addControl(lineControl);
lineControl.activate();
var handler;
for (var i = 0; i < map.controls.length; i++) {
var control = map.controls[i];
if (control.displayClass === "olControlDrawFeature") {
handler = control.handler;
break;
}
}
// Here I have an exception in the console : I would like
// OL to draw hat point automatically.
handler.addPoint(new OpenLayers.Pixel(50, 50));
</script>
OpenLayers.Handler.Path.addPoint works on OpenLayers.Pixel, not OpenLayers.LonLat:
/**
* Method: addPoint
* Add point to geometry. Send the point index to override
* the behavior of LinearRing that disregards adding duplicate points.
*
* Parameters:
* pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
*/
addPoint: function(pixel) {
this.layer.removeFeatures([this.point]);
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
}
I actually see no good way of achieving this other than adding an addPointByLonLat method:
OpenLayers.Handler.Path.prototype.addPointByLonLat = function(lonLat) {
this.layer.removeFeatures([this.point]);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
};
Or subclass as your own handler class (propbably cleaner).
Notes:
addPoint is not an API method (so addPointByLonLat is also not). This may result in problem on version changes.
Don't use the compressed/minified JS in development and check docs on methods you use.
Next time consider asking on https://gis.stackexchange.com/.
Consider asking for a code review on your JS.
You can also use insertXY(x,y) function in order to insert a point with geographic coordinates
http://dev.openlayers.org/docs/files/OpenLayers/Handler/Path-js.html#OpenLayers.Handler.Path.insertXY
lonlat = new OpenLayers.LonLat(1,45);
lonlat.transform(CONSTANTS.MAP_FROM_PROJECTION,CONSTANTS.MAP_TO_PROJECTION);
handler.createFeature(new OpenLayers.Pixel(100, 100));
handler.insertXY(lonlat.lon,lonlat.lat);
handler.drawFeature();
You can check it here with a fork of your original jsfiddle : http://jsfiddle.net/mefnpbn2/
See this fiddle for the solution.
tldr :
// draw the first point as I needed, as if a user has clicked on the map
handler.modifyFeature(new OpenLayers.Pixel(50, 50), true);
// draw a first point on the map (not clicked).
// This will be the initial point where the "cursor" is on the map, as long as
// the user hasn't hovered onto the map with its mouse. This make the blue
// line showing current segment to appear, without this segment is drawn but
// no feedback is given to the user as long as he hasn't clicked.
handler.addPoint(new OpenLayers.Pixel(50, 50)); //
This is my second time using three.js and I've been playing around for at least 3 hours. I cannot seem to find a direction.
What I should build is something like this:
https://www.g-star.com/nl_nl/newdenimarrivals
I created the scene and everything, but I cannot seem to find a formula or anything on how to arrange the products like that (not to mention that I have to handle click events afterwards and move the camera to that product).
Do you guys have any leads or anything?
EDIT:
This is how I try to arrange the products.
arrangeProducts: function () {
var self = this;
this.products.forEach(function (element, index) {
THREE.ImageUtils.crossOrigin = '';
element.image = 'http://i.imgur.com/CSyFaYS.jpg';
//texture
var texture = THREE.ImageUtils.loadTexture(element.image, null);
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
//material
var material = new THREE.MeshBasicMaterial({
map: texture
});
//plane geometry
var geometry = new THREE.PlaneGeometry(element.width, element.height);
//plane
var plane = new THREE.Mesh(geometry, material);
plane.overdraw = true;
//set the random locations
/*plane.position.x = Math.random() * (self.container.width - element.width);
plane.position.y = Math.random() * (self.container.height - element.height);*/
plane.position.z = -2500 + (Math.random() * 50) * 50;
plane.position.x = Math.random() * self.container.width - self.container.width / 2;
plane.position.y = Math.random() * 200 - 100;
//add the plane to the scene
self.scene.add(plane);
});
},
EDIT 2:
I figured out: I need to add about 5 transparent concentric cilinders and put the products on each (random location) and have the camera in the center of all the cilinders and just rotate. Buut, how do I put the images on the cilinider randomly? I really have a blockout on that
On the three.js website you find a whole bunch of examples that can show you what is possible and how to do it. Don't expect to be an expert in only 3 hours.
I've been using Jonas Wagner's JS Box2D port and am running into a strange problem with shape userdata. I've setup my entity to have a collision shape as well as a secondary 'foot' sensor shape to determine when my object is on solid ground. The definition looks a little like this:
var bodyDef = new b2.BodyDef();
bodyDef.position.Set(
(this.pos.x + this.size.x / 2) * b2.SCALE,
(this.pos.y + this.size.y / 2) * b2.SCALE
);
this.body = ig.world.CreateBody(bodyDef);
var shapeDef = new b2.PolygonDef();
shapeDef.SetAsBox(
this.size.x / 2 * b2.SCALE,
this.size.y / 2 * b2.SCALE
);
shapeDef.density = 0.85;
shapeDef.filter.groupIndex = -1;
this.body.CreateShape(shapeDef);
var footShapeDef = new b2.PolygonDef();
footShapeDef.SetAsOrientedBox(
3 * b2.SCALE,
3 * b2.SCALE,
new b2.Vec2(0,7*b2.SCALE),
0
);
footShapeDef.isSensor = true;
footShapeDef.density = 0.85;
footShapeDef.filter.groupIndex = -1;
var footShape = this.body.CreateShape(footShapeDef);
footShape.SetUserData("feet");
this.body.SetMassFromShapes();
The idea here being that I can detect when my foot sensor stops colliding with entities. Everything is working as expected and my b2.ContactListener is correctly reporting when objects stop colliding with my foot sensor. The problem is that the userData I'm assigning to my foot shape isn't being correctly reported.
As you can see below, the point object returned in my b2.ContactListener's Remove callback clearly contains a shape (shape2) with it's m_userData attribute set to 'feet'. However, querying the shape2 object directly reports it's m_userData as null.
I've included a screenshot of Safari's debug console performing the console.log shown below. What's going on here?!
var listener = new b2.ContactListener();
listener.Remove = function(point)
{
var p = point;
var s1 = p.shape1;
var s2 = p.shape2;
console.log(p, s1, s2);
}