I am trying to create a random terrain in THREE JS using the plane geometry and randomizing the pos array using this method:
let side = 200;
const geometry = new PlaneGeometry(80, 20, side, 100);
// const material = new MeshStandardMaterial({ color: '#271033'});
let material = new THREE.MeshStandardMaterial({
roughness: 1,
color: new THREE.Color('#271033'),
flatShading: true,
});
const plane = new Mesh(geometry, material);
plane.rotation.x = - Math.PI / 2 - Math.PI / 20;
plane.position.y = - 4;
plane.position.z = - 5;
plane.castShadow = true;
plane.receiveShadow = true;
let pos = geometry.getAttribute("position");
let pa = pos.array as any;
const hVerts = geometry.parameters.heightSegments + 1;
const wVerts = geometry.parameters.widthSegments + 1;
let noise2D = createNoise2D();
for (let j = 0; j < hVerts; j++) {
for (let i = 0; i < wVerts; i++) {
const ex = 1.1;
pa[3 * (j * wVerts + i) + 2] =
( noise2D(i / 100, j / 100) + noise2D((i + 100) / 50, j / 50) * Math.pow(ex, 0) );
}
}
When I try to sample points on the mesh(so that I can place tree's or whatever at said point) the points don't seem to be valid points on the Mesh. I believe it may be return points from a plane that didn't receive the rotation/position transform, but I'm not sure.
Here is the code for the sampling:
const plane = createPlane();
plane1Sampler = new MeshSurfaceSampler(plane).build();
// add plane to scene etc...
for ( let i = 0; i < 10; i ++ ) {
const tree = await loadTree();
const _position = new THREE.Vector3();
const _normal = new THREE.Vector3();
plane1Sampler.sample( _position, _normal );
tree.scale.set(0.1, 0.1, 0.1);
tree.position.set(_position.x, _position.y, _position.z);
this.scene.add( tree );
}
Finally, here's a picture of the result, the tree's should be positioned on the first lighter purple plane. I'm not really sure what the issue here is so any help is very much appreciated! Also, the project is in React and TS.
For whatever reason the sampler was indeed not sampling from a plane with the translations applied. By applying the same translation to the points I was able to get valid points.
// translation applied to the plane, which for some reason the sampler doesn't take into account
_position.applyAxisAngle(new Vector3(1, 0, 0), - Math.PI / 2 - Math.PI / 20);
_position.y -= 6.1;
_position.z -= 5;
Related
I'm working with react-three-fiber and struggling with Line2 and some memory issues. I left my application running for a time and then when I came back my computer was frozen... After I recovered I realized that the the application was gradually consuming more and more memory and sometimes, but not always, showing this error: "webgl warning: drawelementsinstanced: instance fetch requires 30, but attribs only supply 20". I tried changing the getPoints divisions and the number of points in the line itself and I was able to make things a little bit better, but it still didn't solve the issue. The problem seems to be comming from the 'getPoints' method, but I'm not quite so sure. Can someone help me?
export function SingleTrace({
particleSystem,
physics,
steps,
detail,
width,
color,
}) {
const line = useRef();
let positions = useMemo(() => [], [physics]);
useFrame(() => {
let stepsSoFar = positions.length;
if (stepsSoFar === 0) {
let randomX = ((Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x) as number;
let randomY = ((Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y) as number;
let randomZ = ((Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z) as number;
positions = [vec(randomX, randomY, randomZ)]; //otherwise just use a random position
} else if (stepsSoFar > steps) {
positions.shift();
}
let lastPosition = positions[positions.length - 1];
let totalField = vec();
particleSystem.particles.forEach((particle) => {
let field = particle["physics"][physics].field(
lastPosition
);
totalField.add(field);
});
let newPosition = vec()
.copy(lastPosition)
.add(vec().copy(totalField).setLength(detail));
if (particleSystem.boundary.contains(newPosition)) {
positions.push(newPosition);
}
let willRestart = Math.random() < 0.05 ? true : false;
if (willRestart) {
let randomX =
(Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x;
let randomY =
(Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y;
let randomZ =
(Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z;
positions = [vec(randomX, randomY, randomZ)];
}
if (line.current && positions.length > 2) {
let curve = new THREE.CatmullRomCurve3(positions).getPoints(
positions.length
);
let convertedToArray = [];
curve.forEach((item) => convertedToArray.push(item.x, item.y, item.z));
line.current.setPositions(convertedToArray);
}
});
return (
<>
<line2>
<lineGeometry ref={line} />
<lineMaterial
color={color}
linewidth={width}
resolution={new THREE.Vector2(10, 10)}
/>
</line2>
</>
);
}
P.S. vec() is just new THREE.Vector3(0,0,0)
When I click on the spin button, I want the value to be highlighted one by one in the canvas.
Here you can see the whole code: https://codesandbox.io/s/spinning-wheel-react-forked-s528m
renderWheel() {
// determine number/size of sectors that need to created
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
this.setState({
angle: arcSize
});
// dynamically generate sectors from state list
let angle = 0;
for (let i = 0; i < numOptions; i++) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
}
}
renderSector(index, text, start, arc) {
// create canvas arc for each list element
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height / 2;
let radius = 200;
let startAngle = start;
let endAngle = start + arc - 0.02;
let angle = index * arc;
let baseSize = radius * 1.25;
let textRadius = baseSize - 48;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, false);
ctx.lineWidth = radius * 0.25;
ctx.strokeStyle = "white";
ctx.font = "25px Arial";
ctx.fillStyle = "blue";
ctx.stroke();
ctx.save();
ctx.translate(
baseSize + Math.cos(angle - arc / 2) * textRadius,
baseSize + Math.sin(angle - arc / 2) * textRadius
);
ctx.rotate(0);
ctx.fillText(text, -ctx.measureText(text).width / 2, 7);
ctx.restore();
}
When I click on the spin button, this spin () method is executed.
The iteration ends between two and five seconds as written in the setTimeout method.
spin = () => {
var index = 0;
let timer = setInterval(() => {
index = (index) % this.state.list.length + 1;
console.log(index)
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Remember: In canvas you cannot access a particular object, so the solution is always to erase and redraw. Don't worry about performance, this is pretty great. I leave you an idea with your code and at the end some suggestions. Try this code, the only thing I did is clean the canvas and redraw without the setTimeOut index
spin = () => {
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
var index = 0;
index = (index % this.state.list.length) + 1;
let angle = 0;
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
let timer = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.setState({
angle: arcSize
});
for (let i = 0; i < numOptions; i++) {
if (i !== index) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
} else {
}
}
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Don't use "getElementById" in react, it's dangerous. Use a reference.
When running this script on p5 js's sandbox it's working really fine
When trying to use it inside react i get : Expected an assignment or function call and instead saw an expression no-unused-expressions
I checked previous questions and found out that most of the problems are syntax ones due to not using brackets or semicolons . I did all the good syntax practices but didn't get to make it function
Here is my sketch code on react :
export default function sketch(p){
var t;
var canvas ;
var w = window.innerWidth;
var h = window.innerHeight;
var x , y , d , T;
p.setup=()=>{
// put setup code here
t=0;
}
p.draw = () => {
canvas =p.createCanvas(w-50, h-50);
canvas.style('display','block');
var x_center = (p.windowWidth - w) / 2;
var y_center = (p.windowHeight - h) / 2;
canvas.position(x_center, y_center);
t += .01;
for (var j = 2; x = y = j--;){
for (var i = w; d = p.noise(t - i / 99) * 3, i--; x += Math.cos(d) * 6, y += Math.sin(d) * 6){
j ? i - w / 2 || ( T = p.translate)(-x, -y) : p.push() + T(x + w / 2, y + w / 2) + p.rotate(d + Math.PI / 4) + p.line(-600, 100, 600, 100) + p.pop(i % 100 || p.scale(80));
}
}
}
window.onresize = function() {
// assigns new values for width and height variables
w = window.innerWidth;
h = window.innerHeight;
canvas.size(w,h);
}
}
Write out p.translate(x, y) every time rather than using T = p.translate and then T(x, y), e.g.,
export default function sketch(p){
let t;
let canvas ;
let w = window.innerWidth;
let h = window.innerHeight;
let x ; let y ; let d ;
p.setup=()=>{
canvas =p.createCanvas(w-50, h-50);
canvas.style('display','block');
// put setup code here
t=0;
const x_center = (p.windowWidth - w) / 2;
const y_center = (p.windowHeight - h) / 2;
canvas.position(x_center, y_center);
}
p.draw = () => {
p.background(255);
t += .0008;
for (let j = 2; x = y = j--;){ // j >0
for (let i = w-200; d = p.noise(t - i / 99) * 3, i--; x += Math.cos(d) * 6, y += Math.sin(d) * 6, i>0){ // i >0
j ? i - w / 2 || p.translate(-x, -y) : p.push() + p.translate(x + w / 2, y + w / 2) + p.rotate(d + Math.PI / 4) + p.line(-600, 100, 600, 100) + p.pop(i % 100 || p.scale(80));
}
}
}
}
Your initial version does not work because in instance mode translate() is a method belonging to p, and thus wants to access other methods or fields of p. The function T that you defined along the way, however, does not belong to p and thus does not have access to any this.foos.
I have a working raphael.js fiddle : http://jsfiddle.net/El4a/sbxjfafx/10/
Now, I need the created svg inside my angular-meteor project. I need it on several places so it has to be abstract. So first question already: should I use a factory or a directive for this?
I've been trying to make it a factory for now, but to be honest, I have no idea what I'm doing. I don't have any errors, but the image doesn't show up.
This is the raphael code inside a factory :
'use strict';
angular.module('timeAppApp').factory('svgFactory', function($rootScope){
Raphael.fn.pieChart = function (cx, cy, r, values) {
var paper = this,
rad = Math.PI / 180,
chart = this.set();
function sector(cx, cy, r, startAngle, endAngle, params) {
var x1 = cx + r * Math.cos(-startAngle * rad),
x2 = cx + r * Math.cos(-endAngle * rad),
y1 = cy + r * Math.sin(-startAngle * rad),
y2 = cy + r * Math.sin(-endAngle * rad);
return paper.path(["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2, "z"]).attr(params);
}
var bcolors = ['#d667cd','#3D8C1E','#00b9ff'];
var colors = ['FF5DF4','#69F233', '#0080B0'];
var angle = 0,
total = 0,
start = 0,
process = function (j) {
var value = values[j],
angleplus = 360 * value / total,
popangle = angle + (angleplus / 2),
// color = "hsb(" + start + ", 1, .5)",
color = colors[j%colors.length],
ms = 500,
delta = 30,
//bcolor = "hsb(" + start + ", 1, 1)",
bcolor = bcolors[j%bcolors.length],
p = sector(cx, cy, r, angle, angle + angleplus, {gradient: "100-" + bcolor + "-" + color}),
txt = paper.text(cx + (r + delta + 55) * Math.cos(-popangle * rad), cy + (r + delta + 25) * Math.sin(-popangle * rad));
angle += angleplus;
chart.push(p);
chart.push(txt);
start += .1;
};
for (var i = 0, ii = values.length; i < ii; i++) {
total += values[i];
}
for (var i = 0; i < ii; i++) {
process(i);
}
return chart;
};
Raphael.fn.circle = function(cx, cy, r){
var paper = this,
rad = Math.PI / 180,
chart = this.set();
var circle = paper.circle(280, 180, 175);
circle.attr("fill", "white");
circle.attr("stroke", "#fff");
return chart;
};
var r;
var svg = function (raphael) {
$(function () {
var values = [20, 60, 20];
r = raphael("svg", 700, 700);
r.pieChart(350, 350, 100, values, "#fff");
r.circle(350, 350, 85).attr({ fill: 'white' });
})
};
svg(raphael);
return{r : r};
When I debug in the browser, it shows that all the raphael functions get skipped. I havent a clue as to why.
The console says : "ReferenceError: raphael is not defined",
but when I watch Raphael I get a running version :
"Raphael: Your browser supports SVG. You are running Raphaƫl 2.1.2"
So I'm trying to get the div with id "svg", and draw the image inside that. This does not happen and the div remains empty.
I have injected the new factory
angular.module('timeAppApp')
.controller('ProjectDetailController', function($scope, $meteor, $state, $stateParams, svgFactory)
and try to draw the image in the matching view. <div id="svg"></div>
I know I'm probably doing 127things wrong and raphael/angular enthousiast are crying blood, but I really don't know what :(
As an added plus, I also kinda need to be able to pass 3 values to the factory, instead of having them hardcoded.
Thanks in advance for drudging through this mess!
I'm not sure how, but it all worked out. I ditched the factory idea, and used a directive. This worked without any issues.
the following flash actionscript below stores letters and numbers into an array and displays them randomly in a flash movie as "falling text" down the screen. I'd like to modify this script so I can have the array store multiple images verses letters and numbers.
source:
http://www.flashcomponents.net/tutorials/letter_and_number_rain_flash_tutorial/page_1.html
--- layer script
var letter_array = new Array("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","1","2","3","4","5","6","7","8","9","0");
var i = 0;
setInterval(nLetter, 70);
function nLetter()
{
duplicateMovieClip(letterMC, "letter" + i, 10 + i);
i++
}
-------- symbol script
onClipEvent(load)
{
var index = Math.round(Math.random() * _root.letter_array.length - 1)
this.letter.text = _root.letter_array[index];
_x = Math.round(Math.random() * 540) + 5
_y = Math.round(Math.random() * -20) - 10;
var gravity = 0.2;
var ran = Math.round(Math.random() * 50) + 50;
_alpha = ran + 10;
_xscale = _yscale = ran;
speed = Math.round(Math.random() * 5) + 10;
rotSpeed = Math.round(Math.random() * 6) - 3;
//this.letter.textColor = Math.random() * 0xFFFFFF
}
onClipEvent(enterFrame)
{
_y += speed;
_rotation += rotSpeed
speed += gravity;
if(_y > 410)
{
this.removeMovieClip();
}
}
You need to add this at the top of your code:
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.*;
In the layer script you need to have an array of images, so the first thing would be to load all your images and store them in an array when the loading is complete.
in the following code , replace i "inferior to" totalNumImages with
i < totalNumImages , I can't use the "<" character in the code.
//your images location
var url1:String = "images/image1.jpg";
var url2:String = "images/image2.jpg";
var urls:Array = [ url1 , url2 ];
var images:Array = [];
//the total number of images
var totalNumImages:int = 2;
for(var i:int ;i "inferior to" totalNumImages ; ++i )
{
loadImage(images[i] );
}
function loadImage(url:String):void
{
var loader:Loader = new Loader();
var info:LoaderInfo = loader.contentLoaderInfo;
info.addEventListener(Event.COMPLETE , loadComplete );
info.addEventListener(IOErrorEvent.IO_ERROR , errorHandler );
loader.load ( new URLRequest(url ));
}
function errorHandler(event:IOErrorEvent):void
{
trace( event );
}
function loadComplete(event:Event):void
{
//assuming the order doesn't matter
images.push(event.currentTarget.loader.content );
//all images are loaded, let's start...
if(images.length == totalNumImages )
nLetter();
}
After all your images have loaded you can start the nLetter() function. Each time a MovieClip is added to the stage, it will load its image from the array of images that you've created.
change this:
this.letter.text = _root.letter_array[index];
to this:
//assumes that you have an array of DisplayObject
addChild( _root.images[index]);