Three.js - Fisheye effect - camera

So, I've messed around with three.js, works out great. The only thing I can't figure out, is how to make a camera with a real fisheye effect.
How is that possible? camera.setLens()?

The fish eye effect can be achieved using Giliam de Carpentier's shader for lens distortion.
Shader code:
function getDistortionShaderDefinition()
{
return {
uniforms: {
"tDiffuse": { type: "t", value: null },
"strength": { type: "f", value: 0 },
"height": { type: "f", value: 1 },
"aspectRatio": { type: "f", value: 1 },
"cylindricalRatio": { type: "f", value: 1 }
},
vertexShader: [
"uniform float strength;", // s: 0 = perspective, 1 = stereographic
"uniform float height;", // h: tan(verticalFOVInRadians / 2)
"uniform float aspectRatio;", // a: screenWidth / screenHeight
"uniform float cylindricalRatio;", // c: cylindrical distortion ratio. 1 = spherical
"varying vec3 vUV;", // output to interpolate over screen
"varying vec2 vUVDot;", // output to interpolate over screen
"void main() {",
"gl_Position = projectionMatrix * (modelViewMatrix * vec4(position, 1.0));",
"float scaledHeight = strength * height;",
"float cylAspectRatio = aspectRatio * cylindricalRatio;",
"float aspectDiagSq = aspectRatio * aspectRatio + 1.0;",
"float diagSq = scaledHeight * scaledHeight * aspectDiagSq;",
"vec2 signedUV = (2.0 * uv + vec2(-1.0, -1.0));",
"float z = 0.5 * sqrt(diagSq + 1.0) + 0.5;",
"float ny = (z - 1.0) / (cylAspectRatio * cylAspectRatio + 1.0);",
"vUVDot = sqrt(ny) * vec2(cylAspectRatio, 1.0) * signedUV;",
"vUV = vec3(0.5, 0.5, 1.0) * z + vec3(-0.5, -0.5, 0.0);",
"vUV.xy += uv;",
"}"
].join("\n"),
fragmentShader: [
"uniform sampler2D tDiffuse;", // sampler of rendered scene?s render target
"varying vec3 vUV;", // interpolated vertex output data
"varying vec2 vUVDot;", // interpolated vertex output data
"void main() {",
"vec3 uv = dot(vUVDot, vUVDot) * vec3(-0.5, -0.5, -1.0) + vUV;",
"gl_FragColor = texture2DProj(tDiffuse, uv);",
"}"
].join("\n")
};
}
One way to setup the effect using effect composer (assuming scene and renderer have been been created):
// Create camera
camera = new THREE.PerspectiveCamera( 100, window.innerWidth / window.innerHeight, 1, 1000000 );
camera.position.z = 800;
// Create effect composer
composer = new THREE.EffectComposer( renderer );
composer.addPass( new THREE.RenderPass( scene, camera ) );
// Add distortion effect to effect composer
var effect = new THREE.ShaderPass( getDistortionShaderDefinition() );
composer.addPass( effect );
effect.renderToScreen = true;
// Setup distortion effect
var horizontalFOV = 140;
var strength = 0.5;
var cylindricalRatio = 2;
var height = Math.tan(THREE.Math.degToRad(horizontalFOV) / 2) / camera.aspect;
camera.fov = Math.atan(height) * 2 * 180 / 3.1415926535;
camera.updateProjectionMatrix();
effect.uniforms[ "strength" ].value = strength;
effect.uniforms[ "height" ].value = height;
effect.uniforms[ "aspectRatio" ].value = camera.aspect;
effect.uniforms[ "cylindricalRatio" ].value = cylindricalRatio;
Following script are needed and they can be found for example from three.js GitHub page:
<script src="examples/js/postprocessing/EffectComposer.js"></script>
<script src="examples/js/postprocessing/RenderPass.js"></script>
<script src="examples/js/postprocessing/MaskPass.js"></script>
<script src="examples/js/postprocessing/ShaderPass.js"></script>
<script src="examples/js/shaders/CopyShader.js"></script>
Link to Giliam's example: http://www.decarpentier.nl/downloads/lensdistortion-webgl/lensdistortion-webgl.html
Link to Giliam's article about lens distortion: http://www.decarpentier.nl/lens-distortion
Image of my test where lens distortion effect is used:

Put a camera inside a reflective sphere. Make sure the sphere is double sided. Parent the camera and sphere together if you want to move it around your scene. Works like a charm:
http://tileableart.com/code/NOCosmos/test.html
borrowed from:
http://mrdoob.github.io/three.js/examples/webgl_materials_cubemap_dynamic2.html
cubeCamera = new THREE.CubeCamera( 1, 3000, 1024);
cubeCamera.renderTarget.minFilter = THREE.LinearMipMapLinearFilter;
scene.add( cubeCamera );
camParent.add(cubeCamera);
var material = new THREE.MeshBasicMaterial( { envMap: cubeCamera.renderTarget } );
material.side = THREE.DoubleSide;
sphere = new THREE.Mesh( new THREE.SphereGeometry( 2, 60, 30 ), material );

It's possible to get the fisheye effect with a high Field of View.
var fishCamera = new THREE.PerspectiveCamera( 110, window.innerWidth / window.innerHeight, 1, 1100 );
var normalCamera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1100 );
or set
camera.fov = 110
camera.updateProjectionMatrix();
Live Example here:
http://mrdoob.github.com/three.js/examples/canvas_geometry_panorama_fisheye.html

One way is to set a large field of view on the camera:
new THREE.PerspectiveCamera(140, ... )
This will not technically be a fisheye effect, but it may be the effect you're looking for.
In a real camera lens, getting a large field of view without distorsion would likely make the lens pretty expensive, but in computer graphics, it's the easy way.
A real fisheye lens distorts the image so that straight line become curved, like in this image:
If you want to create an actual fisheye effect with this kind of distorsion, you would have to modify the geometry, as in Three.js's fisheye example. In that example, the geometry is actually modified beforehand, but for a more advanced scene, you'd want to use a vertex shader to update the vertices on the fly.

A wide angle lens generally have a very low focus length.
To achieve an extreme wide angle we need to reduce focus length.
Note that fish eye lens is an extreme wide angle lens.
To reduce focus length(or to achieve extreme wide angles), one can just increase FOV (field of view), as FOV is inversely proportional to focus length.
example:
var camera_1 = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
var camera_2 = new THREE.PerspectiveCamera( 80, width / height, 1, 1000 );
Here camera_2 is a wider angle setup.
Note
To achieve desired effect, one may have to adjust camera position.

Related

How to make Three.js ShaderMaterial gradient to transparent

I want to make a two-color gradient transparent. In the image below you can see.
Left is the final mesh and on the right a single face. I'm trying to achieve this with a vertex shader and a fragment shader. But unfortunately, I can't figure it out. Hopefully, somebody can help me
I have this so far:
var custom3Material = new this.$three.ShaderMaterial({
uniforms: {
vlak3color1: { value: new this.$three.Color('#31c7de')},
vlak3color2: {type: 'vec2', value: new this.$three.Color('#de3c31')},
positionVlak3: {value: -3.5},
},
vertexShader: `
varying vec3 vUv;
void main() {
vUv = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 vlak3color1;
uniform vec3 vlak3color2;
uniform float positionVlak3;
varying vec3 vUv;
void main() {
gl_FragColor = vec4(mix(vlak3color1, vlak3color2, vUv.y-positionVlak3), 1);
}
`,
});
I would like to be able to adjust the position between the 2 colors and the transparency afterward
Thanks in advance!
You must make your material transparent by adding transparent: true to its attributes.
vlak3color2: {type: 'vec2', value: new this.$three.Color('#de3c31')} is confusing. Why are you trying to make a color of type vec2? Just get rid of the type, you don't need it. Three.js automatically recognizes the type when it sees it's a Color.
The fourth value of gl_FragColor is the alpha. Right now you're setting it to a constant 1, so you're getting a fully-opaque color. Try to make it fade from 0 - 1 with smoothstep():
void main() {
// y < 0 = transparent, > 1 = opaque
float alpha = smoothstep(0.0, 1.0, vUv.y);
// y < 1 = color1, > 2 = color2
float colorMix = smoothstep(1.0, 2.0, vUv.y);
gl_FragColor = vec4(mix(vlak3color1, vlak3color2, colorMix), alpha);
}

Three.js, Camera rotation around a point

I'm trying to make a camera rotation and zoom around a point.
I don't want to use THREE.TrackBallControls because as i see, it only modify the fov when zooming, it is not what i want.
I'm using this simple equation : http://en.wikipedia.org/wiki/Sphere#Equations_in. To compute a point on the sphere, this point will be the position of the camera.
In three.js
var point = new THREE.Vector3(
radius * Math.cos(theta) * Math.sin(phi),
radius * Math.sin(theta) * Math.sin(phi),
radius * Math.cos(phi)
);
point.add(center);
To manipulate it, i use :
left mouse, for rotation : x coord === theta and y coord === phi
middle mouse, for zooming : zoom in/out === radius
It doesn't work well, especially for 'phi' rotation, maybe because it's define between 0 and PI.
Maybe there is a better solution using Quaternion.Slerp ?
edit :
My camera :
aspectRatio = container.offsetWidth / container.offsetHeight;
camera = new THREE.OrthographicCamera( -aspectRatio * viewSize / 2, aspectRatio * viewSize / 2, viewSize / 2, -viewSize / 2, -10, 10 );
camera.position.x = 0.004;
camera.position.y = 0.004;
camera.position.z = 0.004;
camera.lookAt(new THREE.Vector3(0, 0, 0))
scene.add(camera);
My OrbitControls :
controls = new THREE.OrbitControls ( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.keyPanSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
when using left mouse (rotation) the camera.position change.
when using middle mouse (zoom) the camera.position is still the same.
check made in the render function :
function render() {
// update camera controls
controls.update();
console.log(camera.position);
// actually render the scene
renderer.render( scene, camera );
}

DirectXMath Unproject on the wrong axis

EDIT: Solved the issue, see my own answer
Recently I've been working on a 3D world editor that will be using picking to raise or lower terrain. I'm using camera unprojection and ray casting to find the world position of the mouse screen coordinates.
However, it seems like the ray is on the wrong axis. As I remember, the ray that is supposed to come out of unproject should be coming straight from the camera.
Here's an example of what it currently looks like.
My question is, why is the ray over the Y-axis when it's supposed to be over the Z-axis?
XMFLOAT3 D3D11Camera::Unproject(const float& px, const float& py, const float& pz)
{
const XMFLOAT2& res = D3D11RenderSettings::Instance()->resolution();
XMVECTOR coords = XMVector3Unproject(XMVectorSet(px, res.y - py, pz, 0.0f), 0.0f, 0.0f, res.x, res.y, near_plane_, far_plane_, projection_, view_, XMMatrixIdentity());
XMFLOAT3 to_ret;
XMStoreFloat3(&to_ret, coords);
return to_ret;
}
This is the unprojection code..
And this is how I'm using it
projectRay: function()
{
var p = Mouse.position(MousePosition.Relative);
p.x = (p.x + RenderSettings.resolution().w / 2);
p.y = (p.y + RenderSettings.resolution().h / 2);
var unprojA = this._camera.unproject(p.x, p.y, 0);
var unprojB = this._camera.unproject(p.x, p.y, 1);
var dir = Vector3D.normalise(Vector3D.sub(unprojB, unprojA));
var ray = Ray.construct(unprojA, dir);
var p1 = ray.origin;
var p2 = Vector3D.add(ray.origin, Vector3D.mul(ray.direction, 1000));
RenderTargets.ui.drawLine(p1.x, p1.y, p1.z, 1, 0, 0, p2.x, p2.y, p2.z, 1, 0, 0);
return ray;
}
Cheers!
Stupidity aside, I'm working with both an inverted Y-axis coordinate system and a default -- non inverted -- one. It seemed like it was a ray oriented vertically, but in fact it was a ray that was oriented in the direction of '/' while it was supposed to be oriented like '\'. Multiplying the Y component of the result by -1 solved the issue.

SceneKit – Drawing a line between two points

I have two points (let's call them pointA and pointB) of type SCNVector3. I want to draw a line between them. Seems like it should be easy, but can't find a way to do it.
I see two options, both have issues:
Use a SCNCylinder with a small radius, with length |pointA-pointB| and then position it/rotate it.
Use a custom SCNGeometry but not sure how; would have to define two triangles to form a very thin rectangle perhaps?
It seems like there should be an easier way of doing this, but I can't seem to find one.
Edit: Using the triangle method gives me this for drawing a line between (0,0,0) and (10,10,10):
CGFloat delta = 0.1;
SCNVector3 positions[] = { SCNVector3Make(0,0,0),
SCNVector3Make(10, 10, 10),
SCNVector3Make(0+delta, 0+delta, 0+delta),
SCNVector3Make(10+delta, 10+delta, 10+delta)};
int indicies[] = {
0,2,1,
1,2,3
};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:4];
NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:2 bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource] elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
But there are problems: due to the normals, you can only see this line from one side! It's invisible from the other side. Also, if "delta" is too small you can't see the line at all. As it is, it's technically a rectangle, rather than the line I was going for, which might result in small graphical glitches if I want to draw multiple joined up lines.
Here's a simple extension in Swift:
extension SCNGeometry {
class func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
let indices: [Int32] = [0, 1]
let source = SCNGeometrySource(vertices: [vector1, vector2])
let element = SCNGeometryElement(indices: indices, primitiveType: .Line)
return SCNGeometry(sources: [source], elements: [element])
}
}
There are lots of ways to do this.
As noted, your custom geometry approach has some disadvantages. You should be able to correct the problem of it being invisible from one side by giving its material the doubleSided property. You still may have issues with it being two-dimensional, though.
You could also modify your custom geometry to include more triangles, so you get a tube shape with three or more sides instead of a flat rectangle. Or just have two points in your geometry source, and use the SCNGeometryPrimitiveTypeLine geometry element type to have Scene Kit draw a line segment between them. (Though you won't get as much flexibility in rendering styles with line drawing as with shaded polygons.)
You can also use the SCNCylinder approach you mentioned (or any of the other built-in primitive shapes). Remember that geometries are defined in their own local (aka Model) coordinate space, which Scene Kit interprets relative to the coordinate space defined by a node. In other words, you can define a cylinder (or box or capsule or plane or whatever) that's 1.0 units wide in all dimensions, then use the rotation/scale/position or transform of the SCNNode containing that geometry to make it long, thin, and stretching between the two points you want. (Also note that since your line is going to be pretty thin, you can reduce the segmentCounts of whichever built-in geometry you're using, because that much detail won't be visible.)
Yet another option is the SCNShape class that lets you create an extruded 3D object from a 2D Bézier path. Working out the right transform to get a plane connecting two arbitrary points sounds like some fun math, but once you do it you could easily connect your points with any shape of line you choose.
New code for a line from (0, 0, 0) to (10, 10, 10) below.
I'm not sure if it could be improved further.
SCNVector3 positions[] = {
SCNVector3Make(0.0, 0.0, 0.0),
SCNVector3Make(10.0, 10.0, 10.0)
};
int indices[] = {0, 1};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions
count:2];
NSData *indexData = [NSData dataWithBytes:indices
length:sizeof(indices)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
primitiveType:SCNGeometryPrimitiveTypeLine
primitiveCount:1
bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource]
elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Here's one solution
class func lineBetweenNodeA(nodeA: SCNNode, nodeB: SCNNode) -> SCNNode {
let positions: [Float32] = [nodeA.position.x, nodeA.position.y, nodeA.position.z, nodeB.position.x, nodeB.position.y, nodeB.position.z]
let positionData = NSData(bytes: positions, length: MemoryLayout<Float32>.size*positions.count)
let indices: [Int32] = [0, 1]
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
let source = SCNGeometrySource(data: positionData as Data, semantic: SCNGeometrySource.Semantic.vertex, vectorCount: indices.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float32>.size, dataOffset: 0, dataStride: MemoryLayout<Float32>.size * 3)
let element = SCNGeometryElement(data: indexData as Data, primitiveType: SCNGeometryPrimitiveType.line, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)
let line = SCNGeometry(sources: [source], elements: [element])
return SCNNode(geometry: line)
}
if you would like to update the line width or anything related to modifying properties of the drawn line, you'll want to use one of the openGL calls in SceneKit's rendering callback:
func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
//Makes the lines thicker
glLineWidth(20)
}
Here is a swift5 version:
func lineBetweenNodes(positionA: SCNVector3, positionB: SCNVector3, inScene: SCNScene) -> SCNNode {
let vector = SCNVector3(positionA.x - positionB.x, positionA.y - positionB.y, positionA.z - positionB.z)
let distance = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
let midPosition = SCNVector3 (x:(positionA.x + positionB.x) / 2, y:(positionA.y + positionB.y) / 2, z:(positionA.z + positionB.z) / 2)
let lineGeometry = SCNCylinder()
lineGeometry.radius = 0.05
lineGeometry.height = distance
lineGeometry.radialSegmentCount = 5
lineGeometry.firstMaterial!.diffuse.contents = GREEN
let lineNode = SCNNode(geometry: lineGeometry)
lineNode.position = midPosition
lineNode.look (at: positionB, up: inScene.rootNode.worldUp, localFront: lineNode.worldUp)
return lineNode
}
So inside your ViewController.cs define your vector points and call a Draw function, then on the last line there - it's just rotating it to look at point b.
var a = someVector3;
var b = someOtherVector3;
nfloat cLength = (nfloat)Vector3Helper.DistanceBetweenPoints(a, b);
var cyclinderLine = CreateGeometry.DrawCylinderBetweenPoints(a, b, cLength, 0.05f, 10);
ARView.Scene.RootNode.Add(cyclinderLine);
cyclinderLine.Look(b, ARView.Scene.RootNode.WorldUp, cyclinderLine.WorldUp);
Create a static CreateGeomery class and put this static method in there
public static SCNNode DrawCylinderBetweenPoints(SCNVector3 a,SCNVector3 b, nfloat length, nfloat radius, int radialSegments){
SCNNode cylinderNode;
SCNCylinder cylinder = new SCNCylinder();
cylinder.Radius = radius;
cylinder.Height = length;
cylinder.RadialSegmentCount = radialSegments;
cylinderNode = SCNNode.FromGeometry(cylinder);
cylinderNode.Position = Vector3Helper.GetMidpoint(a,b);
return cylinderNode;
}
you may also want these utility methods in a static helper class
public static double DistanceBetweenPoints(SCNVector3 a, SCNVector3 b)
{
SCNVector3 vector = new SCNVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
}
public static SCNVector3 GetMidpoint(SCNVector3 a, SCNVector3 b){
float x = (a.X + b.X) / 2;
float y = (a.Y + b.Y) / 2;
float z = (a.Z + b.Z) / 2;
return new SCNVector3(x, y, z);
}
For all my Xamarin c# homies out there.
Here's a solution using triangles that works independent of the direction of the line.
It's constructed using the cross product to get points perpendicular to the line. So you'll need a small SCNVector3 extension, but it'll probably come in handy in other cases, too.
private func makeRect(startPoint: SCNVector3, endPoint: SCNVector3, width: Float ) -> SCNGeometry {
let dir = (endPoint - startPoint).normalized()
let perp = dir.cross(SCNNode.localUp) * width / 2
let firstPoint = startPoint + perp
let secondPoint = startPoint - perp
let thirdPoint = endPoint + perp
let fourthPoint = endPoint - perp
let points = [firstPoint, secondPoint, thirdPoint, fourthPoint]
let indices: [UInt16] = [
1,0,2,
1,2,3
]
let geoSource = SCNGeometrySource(vertices: points)
let geoElement = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geo = SCNGeometry(sources: [geoSource], elements: [geoElement])
geo.firstMaterial?.diffuse.contents = UIColor.blue.cgColor
return geo
}
SCNVector3 extension:
import Foundation
import SceneKit
extension SCNVector3
{
/**
* Returns the length (magnitude) of the vector described by the SCNVector3
*/
func length() -> Float {
return sqrtf(x*x + y*y + z*z)
}
/**
* Normalizes the vector described by the SCNVector3 to length 1.0 and returns
* the result as a new SCNVector3.
*/
func normalized() -> SCNVector3 {
return self / length()
}
/**
* Calculates the cross product between two SCNVector3.
*/
func cross(_ vector: SCNVector3) -> SCNVector3 {
return SCNVector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
}
}
Swift version
To generate a line in a form of cylinder with a certain position and an orientation, let's implement the SCNGeometry extension with a cylinderLine() class method inside. The toughest part here is a trigonometry (for defining cylinder's direction). Here it is:
import SceneKit
extension SCNGeometry {
class func cylinderLine(from: SCNVector3, to: SCNVector3,
segments: Int = 5) -> SCNNode {
let x1 = from.x; let x2 = to.x
let y1 = from.y; let y2 = to.y
let z1 = from.z; let z2 = to.z
let subExpr01 = Float((x2-x1) * (x2-x1))
let subExpr02 = Float((y2-y1) * (y2-y1))
let subExpr03 = Float((z2-z1) * (z2-z1))
let distance = CGFloat(sqrtf(subExpr01 + subExpr02 + subExpr03))
let cylinder = SCNCylinder(radius: 0.005, height: CGFloat(distance))
cylinder.radialSegmentCount = segments
cylinder.firstMaterial?.diffuse.contents = NSColor.systemYellow
let lineNode = SCNNode(geometry: cylinder)
lineNode.position = SCNVector3((x1+x2)/2, (y1+y2)/2, (z1+z2)/2)
lineNode.eulerAngles = SCNVector3(x: CGFloat.pi / 2,
y: acos((to.z-from.z)/CGFloat(distance)),
z: atan2((to.y-from.y), (to.x-from.x)))
return lineNode
}
}
The rest is easy.
class ViewController: NSViewController {
#IBOutlet var sceneView: SCNView!
let scene = SCNScene()
var startingPoint: SCNVector3!
var endingPoint: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.black
sceneView.allowsCameraControl = true
self.startingPoint = SCNVector3Zero
self.endingPoint = SCNVector3(1,1,1)
self.lineInBetween()
}
func lineInBetween() {
self.addSphereDot(position: startingPoint)
self.addSphereDot(position: endingPoint)
self.addLine(start: startingPoint, end: endingPoint)
}
func addSphereDot(position: SCNVector3) {
let sphere = SCNSphere(radius: 0.03)
sphere.firstMaterial?.diffuse.contents = NSColor.red
let node = SCNNode(geometry: sphere)
node.position = position
scene.rootNode.addChildNode(node)
}
func addLine(start: SCNVector3, end: SCNVector3) {
let lineNode = SCNGeometry.cylinderLine(from: start, to: end)
scene.rootNode.addChildNode(lineNode)
}
}

Rotating camera around the X-axis (three.js)

I am trying to rotate the camera around to X-axis of the scene.
At this point my code is like this:
rotation += 0.05;
camera.position.y = Math.sin(rotation) * 500;
camera.position.z = Math.cos(rotation) * 500;
This makes the camera move around but during the rotation something weird happens and either the camera flips, or it skips some part of the imaginary circle it's following.
You have only provided a snippet of code, so I have to make some assumptions about what you are doing.
This code:
rotation += 0.05;
camera.position.x = 0;
camera.position.y = Math.sin(rotation) * 500;
camera.position.z = Math.cos(rotation) * 500;
camera.lookAt( scene.position ); // the origin
will cause the "flipping" you refer to because the camera is trying to remain "right side up", and it will quickly change orientation as it passes over the "north pole."
If you offset the camera's x-coordinate like so,
camera.position.x = 200;
the camera behavior will appear more natural to you.
Three.js tries to keep the camera facing up. When you pass 0 along the z-axis, it'll "fix" the camera's rotation. You can just check and reset the camera's angle manually.
camera.lookAt( scene.position ); // the origin
if (camera.position.z < 0) {
camera.rotation.z = 0;
}
I'm sure this is not the best solution, but if anyone else runs across this question while playing with three.js (like I just did), it'll give one step further.
This works for me, I hope it helps.
Rotating around X-Axis:
var x_axis = new THREE.Vector3( 1, 0, 0 );
var quaternion = new THREE.Quaternion;
camera.position.applyQuaternion(quaternion.setFromAxisAngle(x_axis, rotation_speed));
camera.up.applyQuaternion(quaternion.setFromAxisAngle(x_axis, rotation_speed));
Rotating around Y-Axis:
var y_axis = new THREE.Vector3( 0, 1, 0 );
camera.position.applyQuaternion(quaternion.setFromAxisAngle(y_axis, angle));
Rotating around Z-Axis:
var z_axis = new THREE.Vector3( 0, 0, 1 );
camera.up.applyQuaternion(quaternion.setFromAxisAngle(z_axis, angle));
I wanted to move my camera to a new location while having the camera look at a particular object, and this is what I came up with [make sure to load tween.js]:
/**
* Helper to move camera
* #param loc Vec3 - where to move the camera; has x, y, z attrs
* #param lookAt Vec3 - where the camera should look; has x, y, z attrs
* #param duration int - duration of transition in ms
**/
function flyTo(loc, lookAt, duration) {
// Use initial camera quaternion as the slerp starting point
var startQuaternion = camera.quaternion.clone();
// Use dummy camera focused on target as the slerp ending point
var dummyCamera = camera.clone();
dummyCamera.position.set(loc.x, loc.y, loc.z);
// set the dummy camera quaternion
var rotObjectMatrix = new THREE.Matrix4();
rotObjectMatrix.makeRotationFromQuaternion(startQuaternion);
dummyCamera.quaternion.setFromRotationMatrix(rotObjectMatrix);
dummyCamera.up.set(camera)
console.log(camera.quaternion, dummyCamera.quaternion);
// create dummy controls to avoid mutating main controls
var dummyControls = new THREE.TrackballControls(dummyCamera);
dummyControls.target.set(loc.x, loc.y, loc.z);
dummyControls.update();
// Animate between the start and end quaternions
new TWEEN.Tween(camera.position)
.to(loc, duration)
.onUpdate(function(timestamp) {
// Slerp the camera quaternion for smooth transition.
// `timestamp` is the eased time value from the tween.
THREE.Quaternion.slerp(startQuaternion, dummyCamera.quaternion, camera.quaternion, timestamp);
camera.lookAt(lookAt);
})
.onComplete(function() {
controls.target = new THREE.Vector3(scene.children[1].position-0.001);
camera.lookAt(lookAt);
}).start();
}
Example usage:
var pos = {
x: -4.3,
y: 1.7,
z: 7.3,
};
var lookAt = scene.children[1].position;
flyTo(pos, lookAt, 60000);
Then in your update()/render() function, call TWEEN.update();
Full example