Skip to content

Instantly share code, notes, and snippets.

@harunpehlivan
Created May 30, 2021 13:28
Show Gist options
  • Save harunpehlivan/09f540338c8593e63148627a0d671f68 to your computer and use it in GitHub Desktop.
Save harunpehlivan/09f540338c8593e63148627a0d671f68 to your computer and use it in GitHub Desktop.
Travelling Salesman Sketches: Illustrator 3d
console.clear();
const COLORS = [
'hsl(4, 90%, 58%)', 'hsl(291, 64%, 42%)', 'hsl(207, 90%, 54%)',
'hsl(122, 39%, 49%)', 'hsl(54, 100%, 62%)', 'hsl(36, 100%, 50%)',
'hsl(16, 25%, 38%)'
];
const PI = Math.PI;
const PI2 = PI * 2;
class Visualization {
constructor({ plots, tour }) {
this.setMaxes(plots);
this.scale = 0.25;
this.scene = new THREE.Scene();
this.w = window.innerWidth;
this.h = window.innerHeight;
this.buildCamera();
this.buildRenderer();
this.buildLighting();
this.tour = tour;
this.createPlots(plots);
this.createLines();
this.createCone();
this.bindResize();
this.rotation = 0;
this.animate();
}
setMaxes(plots) {
this.xmax = 0;
this.xmin = Infinity;
this.ymax = 0;
this.ymin = Infinity;
plots.forEach((plot) => {
if (plot[0] > this.xmax) this.xmax = plot[0];
if (plot[0] < this.xmin) this.xmin = plot[0];
if (plot[1] > this.ymax) this.ymax = plot[1];
if (plot[1] < this.ymin) this.ymin = plot[1];
});
this.fieldw = this.xmax - this.xmin;
this.fieldh = this.ymax - this.ymin;
}
buildCamera() {
this.camera = new THREE.PerspectiveCamera(40, this.w/this.h, 0.1, 1000);
this.camera.position.y = this.scale * 2;
}
buildRenderer() {
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(this.w, this.h);
document.body.appendChild(this.renderer.domElement);
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
buildLighting() {
var lights = [];
lights[0] = new THREE.PointLight( '#999', 1, 0 );
lights[1] = new THREE.PointLight( '#999', 1, 0 );
lights[2] = new THREE.PointLight( '#999', 1, 0 );
lights[0].position.set( 0, 200, 0 );
lights[1].position.set( 100, 200, 100 );
lights[2].position.set( - 100, - 200, - 100 );
this.scene.add(lights[0]);
this.scene.add(lights[1]);
this.scene.add(lights[2]);
}
createCone() {
for (let i = 0; i <= this.scale; i += 0.01) {
let r = (i - 0.01) * 1.1;
var geometry = new THREE.CylinderGeometry(r, r, 0.005, 32);
var material = new THREE.MeshPhongMaterial({ color: '#444', specular: '#333', transparent: true, opacity: 0.4 });
var cylinder = new THREE.Mesh(geometry, material);
cylinder.position.x = this.centerx;
cylinder.position.y = this.scale - i;
cylinder.position.z = this.centerz;
this.scene.add(cylinder);
}
}
createPlots(points) {
this.plots = []
let tour = this.tour;
let cx = 0, cy = 0;
let maxDist = 0;
let tourPoints = tour.map((idx) => {
let point = points[idx];
cx += point[0], cy += point[1];
return point;
});
cx /= tour.length, cy /= tour.length;
tour.forEach((idx) => {
let point = points[idx];
let dist = Math.hypot(cx - point[0], cy - point[1]);
if (dist > maxDist) maxDist = dist;
})
let maxSize = Math.max(this.fieldw, this.fieldh);
let offx = (maxSize - this.fieldw) / 2;
let offy = (maxSize - this.fieldh) / 2;
this.maxDist = maxDist;
let px = cx - this.xmin;
let py = cy - this.ymin;
let x = (px + offx) / maxSize;
let z = (py + offy) / maxSize;
let y = (1 - Math.hypot(cx - px, cy - py) / maxDist) * 1;
x = (x * 2 - 1) * this.scale;
z = (z * 2 - 1) * this.scale;
y = this.scale;
this.centerx = x;
this.centery = y;
this.centerz = z;
this.createPlot({ x, y, z, i: null, size: 0.005 });
tourPoints.forEach((p, i) => {
let px = p[0] - this.xmin;
let py = p[1] - this.ymin;
let x = (px + offx) / maxSize;
let z = (py + offy) / maxSize;
let y = (1 - Math.hypot(cx - px, cy - py) / this.maxDist);
x = (x * 2 - 1) * this.scale;
z = (z * 2 - 1) * this.scale;
y = y * this.scale;
let touri = tour[i]
this.createPlot({ x, y, z, i: touri});
});
}
createPlot({ x, y, z, i, size = 0.01 }) {
var color = i === null ? 'white' : COLORS[i % COLORS.length];
var geometry = new THREE.SphereGeometry(size);
var material = new THREE.MeshPhongMaterial({
color,
specular: '#333'
});
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = x;
sphere.position.y = y;
sphere.position.z = z;
if (i !== null) this.plots.push(sphere);
this.scene.add(sphere);
}
createLines() {
this.plots.forEach((plot, i) => {
let touri = this.tour[i];
var color = COLORS[touri % COLORS.length];
var lineMaterial = new THREE.LineBasicMaterial({ color });
var lineGeometry = new THREE.Geometry();
let v1 = new THREE.Vector3(plot.position.x, plot.position.y, plot.position.z);
let plot2 = this.plots[(i + 1) % this.plots.length]
let v2 = new THREE.Vector3(plot2.position.x, plot2.position.y, plot2.position.z);
lineGeometry.vertices.push(v1);
lineGeometry.vertices.push(v2);
var line = new THREE.Line(lineGeometry, lineMaterial);
this.scene.add(line);
});
}
animate() {
requestAnimationFrame(this.animate.bind(this));
this.rotation += 0.0125;
this.camera.position.x = Math.cos(this.rotation);
this.camera.position.z = Math.sin(this.rotation);
this.camera.lookAt({ x: this.centerx, y: this.centery * 0.25, z: this.centerz});
// this.camera.lookAt(this.scene.position);
this.renderer.render(this.scene, this.camera);
}
bindResize() {
window.addEventListener('resize', () => {
this.w = window.innerWidth;
this.h = window.innerHeight;
this.camera.aspect = this.w / this.h;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.w, this.h);
}, false);
}
}
function tour1() {
return [
0,1,2,3
];
}
function plots1() {
return [
[0,0],[1,0],[1,1],[0,1]
];
}
let vectorString = window.location.search.match(/vectors=([\d\.,\|]+)/);
vectorString = vectorString ? vectorString[1] : '0,0|1,0|1,1|0,1';
// vectorString = vectorString ? vectorString[1] : '0,0|0.4,0.125|0.6,0.125|0.5,0.45||0,1|1,0';
let orderString = window.location.search.match(/order=([\d,]+)/);
orderString = orderString ? orderString[1] : null;
// orderString = orderString ? orderString[1] : '0,1,2,6,3,5';
let vectors = parseVectors(vectorString);
let ordering = parseOrdering(orderString, vectors);
let app = new Visualization({ plots: vectors || plots1(), tour: ordering || tour1() });
function parseVectors(string) {
return string.split('|').map(s => s.split(',').map((v, i) => {
return parseFloat(v)
}));
}
function parseOrdering(string, vectors) {
if (string) {
return string.split(',').map((o => parseInt(o)));
} else {
return vectors.map((v, i) => i);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/111863/THREE-OrbitControls.js"></script>
html, body {
height: 100%;
}
body { margin: 0; }
canvas {
width: 100%;
height: 100%;
}

Travelling Salesman Sketches: Illustrator 3d

Takes vector params as vectors=0,0|0,1|1,1|1,0 in an order=01,2,3 and draws them in three dimensions.

A Pen by HARUN PEHLİVAN on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment