I have a simple scene in WebGL where i store every transformation (for the camera and the models) in a single model/view matrix and i set them by rotating and moving said matrix.
What i want is, to being able to rotate the camera around and when i "move forward" to move towards where the camera is pointing.
So far, i have modified this code to this:
mat4.identity(mvMatrix);
mat4.rotateX(mvMatrix, degToRad(elev), mvMatrix);
mat4.rotateY(mvMatrix, degToRad(ang), mvMatrix);
mat4.rotateZ(mvMatrix, degToRad(-roll), mvMatrix);
mat4.translate(mvMatrix, [-px, -py, -pz], mvMatrix);
since it wasn't working as it was and it kind of works, until you do an extreme rotation (more than 90 degrees).
This is not a deal breaker for what i'm doing, but i want to know. Is this the best i can get without moving away from calculating the camera orientation like this?
WebGL cameras generally point down the -Z axis so to move in the direction the camera is facing you just add the camera's Z axis (elements 8, 9, 10) to the position of the camera multiplied by some velocity.
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
varying vec3 v_normal;
void main() {
gl_Position = u_worldViewProjection * position;
v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
}
`;
const fs = `
precision mediump float;
varying vec3 v_normal;
uniform vec3 u_lightDir;
uniform vec4 u_color;
void main() {
vec3 norm = normalize(v_normal);
float light = dot(u_lightDir, norm) * .5 + .5;
gl_FragColor = vec4(u_color.rgb * light, u_color.a);
}
`;
const progInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const projection = m4.identity();
const camera = m4.identity();
const view = m4.identity();
const viewProjection = m4.identity();
const world = m4.identity();
const worldViewProjection = m4.identity();
const worldInverse = m4.identity();
const worldInverseTranspose = m4.identity();
const fov = degToRad(90);
const zNear = 0.1;
const zFar = 100;
const lightDir = v3.normalize([1, 2, 3]);
const keys = {};
let px = 0;
let py = 0;
let pz = 0;
let elev = 0;
let ang = 0;
let roll = 0;
const speed = 1;
const turnSpeed = 90;
let then = 0;
function render(now) {
now *= 0.001; // seconds;
const deltaTime = now - then;
then = now;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(progInfo.program);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
m4.perspective(fov, aspect, zNear, zFar, projection);
m4.identity(camera);
m4.translate(camera, [px, py, pz], camera);
m4.rotateX(camera, degToRad(elev), camera);
m4.rotateY(camera, degToRad(-ang), camera);
m4.rotateZ(camera, degToRad(roll), camera);
m4.inverse(camera, view);
m4.multiply(projection, view, viewProjection);
for (let z = -1; z <= 1; ++z) {
for (let y = -1; y <= 1; ++y) {
for (let x = -1; x <= 1; ++x) {
if (x === 0 && y === 0 && z === 0) {
continue;
}
m4.identity(world);
m4.translate(world, [x * 3, y * 3, z * 3], world);
m4.multiply(viewProjection, world, worldViewProjection);
m4.inverse(world, worldInverse);
m4.transpose(worldInverse, worldInverseTranspose);
twgl.setBuffersAndAttributes(gl, progInfo, bufferInfo);
twgl.setUniforms(progInfo, {
u_worldViewProjection: worldViewProjection,
u_worldInverseTranspose: worldInverseTranspose,
u_color: [(x + 2) / 3, (y + 2) / 3, (z + 2) / 3, 1],
u_lightDir: lightDir,
});
twgl.drawBufferInfo(gl, bufferInfo);
}
}
}
if (keys['87'] || keys['83']) {
const direction = keys['87'] ? 1 : -1;
px -= camera[ 8] * deltaTime * speed * direction;
py -= camera[ 9] * deltaTime * speed * direction;
pz -= camera[10] * deltaTime * speed * direction;
}
if (keys['65'] || keys['68']) {
const direction = keys['65'] ? 1 : -1;
ang += deltaTime * turnSpeed * direction;
}
if (keys['81'] || keys['69']) {
const direction = keys['81'] ? 1 : -1;
roll += deltaTime * turnSpeed * direction;
}
if (keys['38'] || keys['40']) {
const direction = keys['38'] ? 1 : -1;
elev += deltaTime * turnSpeed * direction;
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('keydown', (e) => {
keys[e.keyCode] = true;
e.preventDefault();
});
window.addEventListener('keyup', (e) => {
keys[e.keyCode] = false;
e.preventDefault();
});
function degToRad(d) {
return d * Math.PI / 180;
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
pre { position: absolute; left: 1em; top: 0; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<pre>
A = left
D = right
W = forward
S = down
Q = roll left
E = roll right
UP = look up
DN = look down
</pre>
Related
I have model prediction which detects the end box of verses of quran. I need to shade the area between them as shown in pictures which when the endbox hits the edge it start again from the another edge and the two drawn shadows have the same id. I know it seems complicated but you will save my day if you figure it out here is some of my code to help you reference
"use client";
import LABELS from "#app-datasets/coco/classes.json";
import {
Box,
Button,
ButtonGroup,
Center,
chakra,
Container,
Heading,
Icon,
Text,
useBoolean,
VisuallyHiddenInput,
VStack,
} from "#app-providers/chakra-ui";
import * as tf from "#tensorflow/tfjs";
import "#tensorflow/tfjs-backend-webgl";
import { useEffect, useRef, useState } from "react";
import { FaTimes } from "react-icons/fa";
const ZOO_MODEL = [{ name: "yolov5", child: ["yolov5n", "yolov5s"] }];
function Home() {
// LET RHE USEWER CHOOSE THE MINIMUM SCORE TO SHOW
const [model, setModel] = useState(null);
const [aniId, setAniId] = useState(null);
const [modelName, setModelName] = useState(ZOO_MODEL[0]);
const [loading, setLoading] = useState(0);
const imageRef = useRef(null);
const videoRef = useRef(null);
const canvasRef = useRef(null);
const inputImageRef = useRef(null);
const [singleImage, setSingleImage] = useBoolean();
const [liveWebcam, setliveWebcam] = useBoolean();
useEffect(() => {
tf.loadGraphModel(`/model/${modelName.name}/${modelName.child[1]}/model.json`, {
onProgress: (fractions) => {
setLoading(fractions);
},
}).then(async (mod) => {
// warming up the model before using real data
const dummy = tf.ones(mod.inputs[0].shape);
const res = await mod.executeAsync(dummy);
// clear memory
tf.dispose(res);
tf.dispose(dummy);
// save to state
setModel(mod);
});
}, [modelName]);
// helper for drawing into canvas //IoU threshold 0.5
const renderPrediction = (boxesData, scoresData, classesData , ) => {
const ctx = canvasRef.current.getContext("2d");
// clean canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const font = "16px sans-serif";
ctx.font = font;
ctx.textBaseline = "top";
// LET RHE USEWER CHOOSE minval SCORE TO SHOW
const minval = 0.5;
for (let i = 0; i < scoresData.length; ++i) {
if (scoresData[i] < minval) continue;
const klass = LABELS[classesData[i]];
const score = (scoresData[i] * 100).toFixed(1);
let [x1, y1, x2, y2] = boxesData.slice(i * 4, (i + 1) * 4);
x1 *= canvasRef.current.width;
x2 *= canvasRef.current.width;
y1 *= canvasRef.current.height;
y2 *= canvasRef.current.height;
const width = x2 - x1;
const height = y2 - y1;
// draw the bounding box
ctx.strokeStyle = "#C53030";
ctx.lineWidth = 2;
ctx.strokeRect(x1, y1, width, height);
// fill the area between the boxes
const label = klass + " - " + score + "%";
const textWidth = ctx.measureText(label).width;
const textHeight = parseInt(font, 10); // base 10
// draw the label background
ctx.fillStyle = "#C53030";
ctx.fillRect(x1 - 1, y1 - (textHeight + 4), textWidth + 6, textHeight + 4);
// draw the label text
ctx.fillStyle = "#FFFFFF";
ctx.fillText(label, x1 + 2, y1 - (textHeight + 2));
}
};
I was expecting a shade with the method above
I'm trying to make a FPS camera in WebGL.
When I move the camera from the initial position and then rotate, the camera moves along the start position instead of the new position.
The translation and rotation are made with glMatrix.js library.
I have the variables:
var rotateY = 0; // For rotating along the Y axis
var fronBackMovement = 0; // Front and back movement
var sideMovement = 0; // Movement from side to side
Keyboard events:
// Movement
if( key.w == true ){
fronBackMovement += 5;
}
if( key.a == true ){
sideMovement += 5;
}
if( key.s == true ){
fronBackMovement -= 5;
}
if( key.d == true ){
sideMovement -= 5;
}
Mouse movement:
// Rotation
if( mouse.movementX != 0 ){
rotateY += mouse.movementX; // mouse.movementX is screen movement of the cursor
}
Translation and rotation:
camera.matrix = mat4.fromTranslation( mat4.create() ,vec3.fromValues(sideMovement,0,fronBackMovement) )
camera.matrix = mat4.rotateY(camera.matrix,camera.matrix,rotateY )
The camera generally looks down the -Z axis so to move forward you just add the camera's Z axis to your position. If you don't want to move vertically then zero out the Y component and normalize. Finally multiply by your desired speed.
The camera's x-axis contains the side movement direction so you can do the same for that.
const cameraPosition = vec3.create();
const tempForwardDirection = vec3.create();
const tempSideDirection = vec3.create();
...
tempForwardDirection[0] = camera.matrix[8];
tempForwardDirection[1] = 0; // camera.matrix[9];
tempForwardDirection[2] = camera.matrix[10];
vec3.normalize(tempForwardDirection, tempForwardDirection)
tempSideDirection[0] = camera.matrix[0];
tempSideDirection[1] = camera.matrix[1];
tempSideDirection[2] = camera.matrix[2];
vec3.scaleAndAdd(
cameraPosition,
cameraPosition,
tempForwardDirection,
-fronBackMovement);
vec3.scaleAndAdd(
cameraPosition,
cameraPosition,
tempSideDirection,
sideMovement)
camera.matrix = mat4.fromTranslation(camera.matrix, cameraPosition);
camera.matrix = mat4.rotateY(camera.matrix,camera.matrix,rotateY);
let rotateY = 0; // For rotating along the Y axis
let fronBackMovement = 0; // Front and back movement
let sideMovement = 0; // Movement from side to side
const cameraPosition = vec3.create();
const tempForwardDirection = vec3.create();
const tempSideDirection = vec3.create();
const camera = {
matrix: mat4.create(),
};
const mouse = {
movementX: 0,
};
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
varying vec3 v_normal;
void main() {
gl_Position = u_worldViewProjection * position;
v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
}
`;
const fs = `
precision mediump float;
varying vec3 v_normal;
uniform vec3 u_lightDir;
uniform vec4 u_color;
void main() {
vec3 norm = normalize(v_normal);
float light = dot(u_lightDir, norm) * .5 + .5;
gl_FragColor = vec4(u_color.rgb * light, u_color.a);
}
`;
const progInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const projection = mat4.create();
const view = mat4.create();
const viewProjection = mat4.create();
const world = mat4.create();
const worldViewProjection = mat4.create();
const worldInverse = mat4.create();
const worldInverseTranspose = mat4.create();
const fov = degToRad(90);
const zNear = 0.1;
const zFar = 100;
const lightDir = vec3.normalize(vec3.create(), [1, 2, 3]);
const key = {};
let px = 0;
let py = 0;
let pz = 0;
let elev = 0;
let ang = 0;
let roll = 0;
const speed = 1;
const turnSpeed = 90;
let then = 0;
function render(now) {
now *= 0.001; // seconds;
const deltaTime = now - then;
then = now;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(progInfo.program);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
mat4.perspective(projection, fov, aspect, zNear, zFar);
fronBackMovement = 0;
sideMovement = 0;
if( key.w == true ){
fronBackMovement = 5 * deltaTime;
}
if( key.a == true ){
sideMovement = -5 * deltaTime;
}
if( key.s == true ){
fronBackMovement = -5 * deltaTime;
}
if( key.d == true ){
sideMovement = 5 * deltaTime;
}
// Rotation
if( mouse.movementX != 0 ){
rotateY += mouse.movementX / 200; // mouse.movementX is screen movement of the cursor
mouse.movementX = 0;
}
tempForwardDirection[0] = camera.matrix[8];
tempForwardDirection[1] = 0; // camera.matrix[9];
tempForwardDirection[2] = camera.matrix[10];
vec3.normalize(tempForwardDirection, tempForwardDirection)
tempSideDirection[0] = camera.matrix[0];
tempSideDirection[1] = camera.matrix[1];
tempSideDirection[2] = camera.matrix[2];
vec3.scaleAndAdd(
cameraPosition,
cameraPosition,
tempForwardDirection,
-fronBackMovement);
vec3.scaleAndAdd(
cameraPosition,
cameraPosition,
tempSideDirection,
sideMovement)
camera.matrix = mat4.fromTranslation(camera.matrix, cameraPosition);
camera.matrix = mat4.rotateY(camera.matrix,camera.matrix,rotateY);
mat4.invert(view, camera.matrix);
mat4.multiply(viewProjection, projection, view);
for (let z = -1; z <= 1; ++z) {
for (let y = -1; y <= 1; ++y) {
for (let x = -1; x <= 1; ++x) {
if (x === 0 && y === 0 && z === 0) {
continue;
}
mat4.identity(world);
mat4.translate(world, world, [x * 3, y * 3, z * 3]);
mat4.multiply(worldViewProjection, viewProjection, world);
mat4.invert(worldInverse, world);
mat4.transpose(worldInverseTranspose, worldInverse);
twgl.setBuffersAndAttributes(gl, progInfo, bufferInfo);
twgl.setUniforms(progInfo, {
u_worldViewProjection: worldViewProjection,
u_worldInverseTranspose: worldInverseTranspose,
u_color: [(x + 2) / 3, (y + 2) / 3, (z + 2) / 3, 1],
u_lightDir: lightDir,
});
twgl.drawBufferInfo(gl, bufferInfo);
}
}
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('keydown', (e) => {
key[e.key] = true;
e.preventDefault();
});
window.addEventListener('keyup', (e) => {
key[e.key] = false;
e.preventDefault();
});
window.addEventListener('mousemove', (e) => {
mouse.movementX = e.movementX;
});
function degToRad(d) {
return d * Math.PI / 180;
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
pre { position: absolute; left: 1em; top: 0; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<canvas></canvas>
<pre>
A = left
D = right
W = forward
S = down
</pre>
I created a chart using html canvas. The result would like it to be printed as a PDF file using Kendo. It works, but the graphic quality is very poor. For the solution I need I can't use kendo chart for limitation reasons
report.html
<div class="width-100-perc text-center">
<canvas id="canvas" width="100" height="100"></canvas>
<br />
</div>
report.ts
drawChart() {
console.log( 'foi');
const canvas: HTMLCanvasElement = (<HTMLCanvasElement>document.getElementById('canvas'));
console.log(this.series);
if (canvas) {
const ctx = canvas.getContext('2d');
// Base offset distance of 10
const offset = 0;
let beginAngle = 0;
let endAngle = 0;
// Used to calculate the X and Y offset
let offsetX, offsetY, medianAngle;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill();
for (let i = 0; i < this.angles.length; i = i + 1) {
beginAngle = endAngle;
endAngle = endAngle + this.angles[i];
// The medium angle is the average of two consecutive angles
medianAngle = (endAngle + beginAngle) / 2;
// X and Y calculations
offsetX = Math.cos(medianAngle) * offset;
offsetY = Math.sin(medianAngle) * offset;
ctx.beginPath();
ctx.fillStyle = this.series[0].data[i].color;
// Adding the offsetX and offsetY to the center of the arc
ctx.moveTo(50 + offsetX, 50 + offsetY);
ctx.arc(50 + offsetX, 50 + offsetY, 40, beginAngle, endAngle);
ctx.lineTo(50 + offsetX, 50 + offsetY);
ctx.fill();
}
if (this.angles.length > 0) {
ctx.beginPath();
ctx.fillStyle = '#FFFFFF';
ctx.arc(50, 50, 15, 0, 2 * Math.PI);
ctx.fill();
}
}
}
This is not a problem with kendo's pdf export. Rather, it's inherent to the way the HTML canvas works. Your export looks distorted and pixelated because, at the end of the day, it's just a 100x100 image, which is rather low resolution. I'm assuming you want it to be that small since it is made to fit a specific part of the page. If you just directly export this canvas, that pixelated image is what you should expect.
I can propose this workaround. You need to refactor your drawChart() method to take into account a scale (number). This would mean multiplying all x,y coordinates and dimensions by this value. By default, the scale is 1. When exporting to pdf, you will follow these steps:
Change the scale to higher value, let's say 10
Draw
Export to pdf
Change scale to 1 again
Draw
This way, the chart is temporarily redrawn using a higher resolution canvas. In it's high(er) resolution state, it's exported and then it's redrawn with its original dimensions.
If you provide some example values of your this.angles and this.series I can refactor your drawChart() function to take this into account. As it stands, I can't. But I've prepared a similar example here. This is the ReportComponent I've created.
report.component.html
<button (click)="savePdf(false)">bad pdf</button>
<button (click)="savePdf(true)">good pdf</button>
<br/>
<kendo-pdf-export #pdf>
<canvas #canvas [width]="baseWidth" [height]="baseHeight"></canvas>
</kendo-pdf-export>
report.component.ts
export class ReportComponent implements AfterViewInit {
#ViewChild("canvas", { static: false })
public canvasRef: ElementRef<HTMLCanvasElement>;
#ViewChild("pdf", { static: false })
public pdf: PDFExportComponent;
#Input() public title: string = "";
public scale: number = 1;
public baseWidth: number = 100;
public baseHeight: number = 100;
constructor() {}
ngAfterViewInit() {
this.draw();
}
draw() {
const canvas = this.canvasRef.nativeElement;
canvas.width = this.baseWidth * this.scale; // scaled
canvas.height = this.baseHeight * this.scale; // scaled
const context = canvas.getContext("2d");
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 31.4 * this.scale; // scaled
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = "green";
context.fill();
context.lineWidth = 5 * this.scale; // scaled
context.strokeStyle = "#003300";
context.stroke();
}
savePdf(good: boolean) {
if (good) {
// scale 10x and re-draw
this.scale = 10;
this.draw();
this.pdf.saveAs("good.pdf");
this.scale = 1;
this.draw();
} else {
// just draw as is
this.pdf.saveAs("bad.pdf");
}
}
}
Bad PDF
Good PDF
I'm trying to train a simple sequential model. I want to decompose fit algorithm to gather gradient phase and apply gradient' phase in order to use reinforcement learning.
this idea is taken from cart pole example.
cart pole example
anyway I can not receive good result even if the problem (defined in func()) is comparatively easy to solve by model trained by fit method. The best I can achieve is something like this :
I must be missing something. Could anyone can direct me what's wrong?
Here you can find github source with net output visualization
import {layers, sequential, Sequential} from "#tensorflow/tfjs-layers";
import {
tensor2d,
Tensor,
losses,
variableGrads,
tidy,
train,
NamedTensorMap,
stack,
mean,
concat
} from "#tensorflow/tfjs";
import {ActivationIdentifier} from "#tensorflow/tfjs-layers/src/keras_format/activation_config";
import {NamedTensor} from "#tensorflow/tfjs-core/dist/tensor_types";
import {InitializerIdentifier} from "#tensorflow/tfjs-layers/src/initializers";
import {addHeatmap} from "./vis/heatmap";
const func = (...x) => {
const y1 = x[0] * x[1] * 0.9 + (1 - x[0]) * (1 - x[1]) * 0.9;
return tensor2d([y1], [1, 1])
}
const activation: ActivationIdentifier = "tanh"
const kernelInitializer: InitializerIdentifier = null
const model: Sequential = sequential();
const inputLayer = layers.dense({
units: 2,
inputShape: [2],
kernelInitializer,
});
const hiddenLayer1 = layers.dense({
units: 16,
activation: activation,
//kernelInitializer,
useBias: true
});
const outputLayer = layers.dense({
units: 1,
activation: "sigmoid",
kernelInitializer,
useBias: true
});
const dim = 10; // error sampling density
model.add(inputLayer);
model.add(hiddenLayer1);
model.add(outputLayer);
const optimizer = train.adam(0.1);
const calculateGradient = () => {
return tidy(() => {
const vGrads = variableGrads(() => tidy(() => {
const x1 = Math.random();
const x2 = Math.random();
const labels = func(x1, x2)
const input = tensor2d([x1, x2], [1, 2])
return losses.meanSquaredError(
labels,
model.predict(input) as Tensor
).asScalar();
}));
return vGrads.grads;
})
}
const createBatch = (n: number) => {
return tidy(() => {
const gradientsArrays = {}
for (let i = 0; i < n; i++) {
const gradient = calculateGradient();
Object.keys(gradient).forEach((entry) => {
gradientsArrays[entry] ? gradientsArrays[entry].push(gradient[entry]) : gradientsArrays[entry] = [gradient[entry]]
})
}
const gradientsMeans = {}
Object.keys(gradientsArrays).forEach(key => {
gradientsMeans[key] = mean(stack(gradientsArrays[key], 0))
})
return gradientsMeans;
})
}
const epoch = (iterations: number) => {
for (let i = 0; i < iterations; i++) {
let batch = createBatch(16);
optimizer.applyGradients(batch)
}
}
const calculateDesiredOutputs = () => {
const desiredOutputs = [];
for (let y = 0; y < 1; y += 1 / dim) {
for (let x = 0; x < 1; x += 1 / dim) {
desiredOutputs.push({x, y, value: func(x, y).dataSync()[0]});
}
}
return desiredOutputs;
}
const calculateNetOutputs = () => {
const netOutputs = [];
for (let y = 0; y < 1; y += 1 / dim) {
for (let x = 0; x < 1; x += 1 / dim) {
const value = (<any>model.predict(tensor2d([x, y], [1, 2]))).dataSync()[0];
netOutputs.push({x, y, value});
}
}
return netOutputs
}
const calculateError = (a: { value: number }[], b: { value: number }[]) => {
let error = 0;
for (let i = 0; i < a.length; i++) {
let e = a[i].value - b[i].value;
error += e * e
}
return Math.sqrt(error) / (dim * dim);
}
const run = async () => {
const desiredOutputs = calculateDesiredOutputs();
const desiredOutputsHeatmap = addHeatmap({dim});
desiredOutputsHeatmap.update(desiredOutputs)
const netOutputHeatmap = addHeatmap({dim});
let i = 0;
while (i < 256) {
epoch(20);
let netOutputs = calculateNetOutputs();
console.log("epoch: ", i)
console.log(calculateError(desiredOutputs, netOutputs))
netOutputHeatmap.update(netOutputs);
await new Promise((r) => setTimeout(() => r(), 100));
i++;
}
}
run();
Following the documentation it was easy to figure it out how to click on a mesh, but preventing the camera from going though a mesh not that easy. I need some guidelines.
How can I stop the camera from moving through messes using Raycaster?
jsbin
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - interactive cubes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body style="margin:0;overflow:hidden;">
<div style="position:fixed;background:rgba(255,255,255,0.9);" onmouseout="new function(){controls=new function(){this.moveX=0;this.moveY=0;this.moveZ=0;this.rotateX=0;this.rotateY=0;};}">Move RightMove LeftMove DownMove UpMove BackMove FrontRotate RightRotate LeftRotate UpRotate Down</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
<script>
var container;
var camera, scene, raycaster, renderer;
var mouse = new THREE.Vector2(),INTERSECTED=[],clickedIn/*bc starts like it was clicked*/=false,controls;
var clock = new THREE.Clock();
init();
animate();
function init() {
controls = new function () {
this.moveX = 0;
this.moveY = 0;
this.moveZ = 0;
this.rotateX = 0;
this.rotateY = 0;
}
container = document.createElement('div');
document.body.appendChild(container);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);
var geometry = new THREE.BoxBufferGeometry(20, 20, 20);
for (var i = 0; i < 50; i++) {
var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
object.name = 'Index:' + i;
object.userData.foo = 'foo';
object.position.x = Math.floor(Math.random() * 201) - 100;
object.position.y = Math.floor(Math.random() * 201) - 100;
object.position.z = Math.floor(Math.random() * 201) - 100;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
object.scale.z = Math.random() + 0.5;
scene.add(object);
}
raycaster = new THREE.Raycaster();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xf0f0f0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.sortObjects = false;
container.appendChild(renderer.domElement);
container.addEventListener('click', function (event) {
event.preventDefault();
clickedIn = true;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}, false);
window.addEventListener('resize', function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
}
function animate() {
requestAnimationFrame(animate);
// raycaster
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(scene.children);
for (var i = 0; i < INTERSECTED.length; i++) {
INTERSECTED[i].material.emissive.setHex(INTERSECTED[i].currentHex);
INTERSECTED.splice(i, 1);
}
for (var i = 0; clickedIn && i < intersects.length; i++) {
var length = INTERSECTED.push(intersects[0].object) - 1;
INTERSECTED.currentHex = INTERSECTED[length].material.emissive.getHex();
INTERSECTED[length].material.emissive.setHex(0xff0000);
}
// move
var delta = clock.getDelta(), step = 100, stepAngle = (Math.PI / 2);
if (controls.moveX == 1) camera.translateX(step * delta);
else if (controls.moveX == -1) camera.translateX(-step * delta);
if (controls.moveY == 1) camera.translateY(step * delta);
else if (controls.moveY == -1) camera.translateY(-step * delta);
if (controls.moveZ == 1) camera.translateZ(step * delta);
else if (controls.moveZ == -1) camera.translateZ(-step * delta);
if (controls.rotateX == 1) camera.rotateOnAxis(new THREE.Vector3(1, 0, 0), stepAngle * delta);
if (controls.rotateX == -1) camera.rotateOnAxis(new THREE.Vector3(1, 0, 0), -stepAngle * delta);
if (controls.rotateY == 1) camera.rotateOnAxis(new THREE.Vector3(0, 1, 0), stepAngle * delta);
if (controls.rotateY == -1) camera.rotateOnAxis(new THREE.Vector3(0, 1, 0), -stepAngle * delta);
camera.updateMatrixWorld();
// render
renderer.render(scene, camera);
}
</script>
</body>
</html>
For a First-Person Camera, I think that the right way to do that would be to use the bounding sphere of the camera and test it over each mesh of the scene, but if you really want to use a raycaster then I can think of 2 approaches :
Approach 1
In your rendering loop :
Update the position of your camera ;
For each object in your scene :
Create a ray that goes from the camera to the mesh and starts slightly before the camera;
Cast the ray. If an intersection is found and lies before the camera (1), move the camera to the intersection point.
Approach 2
In your rendering loop again :
Update the position of your camera ;
Create one ray that points towards the direction of the camera and starts slightly before the camera ;
Create another ray that points in the opposite direction of the camera and starts slightly after the camera ;
Cast the first ray. If an intersection is found and the intersection point lies before the camera (1) than move the camera to the intersection point ;
If no intersection is found, cast the second ray. If an intersection is found and the intersection point lies after the camera (2) than move the camera to the intersection point.
The first algorithm is in O(n), n being the number of objects in your scene whereas the second one is in O(1) but can be tricky with big meshes.