···33 Vector3,
44 BufferAttribute,
55 Float32BufferAttribute,
66+ Color,
67} from "three";
7889import { Biome, type BiomeOptions } from "./biome";
···8586 }
8687 >();
87888989+ const calculatedVerticesArray: {
9090+ height: number;
9191+ scatter: Vector3;
9292+9393+ seaHeight: number;
9494+ seaMorph: number;
9595+ }[] = new Array(faceCount);
9696+8897 const colors = new Float32Array(vertices.count * 3);
8998 const oceanColors = new Float32Array(oceanVertices.count * 3);
9099···126135 oceanF = new Vector3();
127136128137 const temp = new Vector3();
138138+139139+ let normHeightMax = 0;
140140+ let normHeightMin = 0;
129141130142 for (let i = 0; i < vertices.count; i += 3) {
131143 a.fromBufferAttribute(vertices, i);
···177189 calculatedVertices.set(key, move);
178190 }
179191192192+ calculatedVerticesArray[i + j] = move;
193193+180194 normalizedHeight += move.height - 1;
181195 v.add(move.scatter).normalize().multiplyScalar(move.height);
182196 vertices.setXYZ(i + j, v.x, v.y, v.z);
···201215 }
202216203217 normalizedHeight /= 3;
204204- normalizedHeight = (normalizedHeight - biome.min) / (biome.max - biome.min);
205205- normalizedHeight = normalizedHeight * 2 - 1;
206206- // now averageHeight is between -1 and 1 (0 is sea level)
207218208208- for (
209209- let j = 0;
210210- biome.options.vegetation && j < biome.options.vegetation.items.length;
211211- j++
212212- ) {
213213- const vegetation = biome.options.vegetation.items[j];
214214- if (Math.random() < faceSize * vegetation.density) {
215215- if (
216216- vegetation.minimumHeight !== undefined &&
217217- normalizedHeight < vegetation.minimumHeight
218218- ) {
219219- continue;
220220- }
219219+ normalizedHeight =
220220+ Math.min(-normalizedHeight / biome.min, 0) +
221221+ Math.max(normalizedHeight / biome.max, 0);
222222+ // now normalizedHeight should be between -1 and 1 (0 is sea level)
221223222222- if (vegetation.minimumHeight === undefined && normalizedHeight < 0) {
223223- continue;
224224- }
225225-226226- if (
227227- vegetation.maximumHeight !== undefined &&
228228- normalizedHeight > vegetation.maximumHeight
229229- ) {
230230- continue;
231231- }
232232- if (!placedVegetation[vegetation.name]) {
233233- placedVegetation[vegetation.name] = [];
234234- }
235235- placedVegetation[vegetation.name].push(a.clone());
236236- break;
237237- }
238238- }
224224+ normHeightMax = Math.max(normHeightMax, normalizedHeight);
225225+ normHeightMin = Math.min(normHeightMin, normalizedHeight);
239226240227 // calculate new normal
241228 temp.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize();
···265252 colors[i * 3 + 6] = color.r;
266253 colors[i * 3 + 7] = color.g;
267254 colors[i * 3 + 8] = color.b;
255255+ }
256256+257257+ // place vegetation
258258+ for (
259259+ let j = 0;
260260+ biome.options.vegetation && j < biome.options.vegetation.items.length;
261261+ j++
262262+ ) {
263263+ const vegetation = biome.options.vegetation.items[j];
264264+ if (Math.random() < faceSize * vegetation.density) {
265265+ // discard if point is below or above height limits
266266+ if (
267267+ vegetation.minimumHeight !== undefined &&
268268+ normalizedHeight < vegetation.minimumHeight
269269+ ) {
270270+ continue;
271271+ }
272272+ // default minimumHeight is 0 (= above sea level)
273273+ if (vegetation.minimumHeight === undefined && normalizedHeight < 0) {
274274+ continue;
275275+ }
276276+ if (
277277+ vegetation.maximumHeight !== undefined &&
278278+ normalizedHeight > vegetation.maximumHeight
279279+ ) {
280280+ continue;
281281+ }
282282+283283+ // discard if point is below or above slope limits
284284+ if (
285285+ vegetation.minimumSlope !== undefined &&
286286+ steepness < vegetation.minimumSlope
287287+ ) {
288288+ continue;
289289+ }
290290+ if (
291291+ vegetation.maximumSlope !== undefined &&
292292+ steepness > vegetation.maximumSlope
293293+ ) {
294294+ continue;
295295+ }
296296+297297+ if (!placedVegetation[vegetation.name]) {
298298+ placedVegetation[vegetation.name] = [];
299299+ }
300300+ let height = a.length();
301301+ placedVegetation[vegetation.name].push(
302302+ a
303303+ .clone()
304304+ .normalize()
305305+ .multiplyScalar(height + 0.005),
306306+ );
307307+308308+ biome.addVegetation(
309309+ vegetation,
310310+ a.normalize(),
311311+ normalizedHeight,
312312+ steepness,
313313+ );
314314+ break;
315315+ }
268316 }
269317270318 // calculate ocean vertices
···300348 oceanMorphNormals.push(temp.x, temp.y, temp.z);
301349 oceanMorphNormals.push(temp.x, temp.y, temp.z);
302350 oceanMorphNormals.push(temp.x, temp.y, temp.z);
351351+ }
352352+353353+ console.log("normHeightMax", normHeightMax);
354354+ console.log("normHeightMin", normHeightMin);
355355+356356+ const maxDist = 0.14;
357357+ // go through all vertices again and update height and color based on vegetation
358358+ for (let i = 0; i < vertices.count; i += 3) {
359359+ let found = false;
360360+ let closestDistAll = 1;
361361+ for (let j = 0; j < 3; j++) {
362362+ a.fromBufferAttribute(vertices, i + j);
363363+ a.normalize();
364364+365365+ let p = biome.itemsAround(a, maxDist);
366366+ if (p.length > 0) {
367367+ // find closest point
368368+ let closest = p[0];
369369+ let closestDist = a.distanceTo(closest);
370370+ for (let k = 1; k < p.length; k++) {
371371+ let dist = a.distanceTo(p[k]);
372372+ if (dist < closestDist) {
373373+ closest = p[k];
374374+ closestDist = dist;
375375+ }
376376+ }
377377+378378+ let moveInfo = calculatedVerticesArray[i + j];
379379+380380+ a.multiplyScalar(
381381+ moveInfo.height + ((maxDist - closestDist) / maxDist) * 0.015,
382382+ );
383383+384384+ vertices.setXYZ(i + j, a.x, a.y, a.z);
385385+386386+ closestDistAll = Math.min(closestDist, closestDistAll);
387387+ found = true;
388388+ }
389389+ }
390390+391391+ if (found) {
392392+ let existingColor = new Color(
393393+ colors[i * 3],
394394+ colors[i * 3 + 1],
395395+ colors[i * 3 + 2],
396396+ );
397397+398398+ // set color
399399+ let newColor = new Color(0.1, 0.3, 0);
400400+401401+ newColor.lerp(existingColor, closestDistAll / maxDist);
402402+403403+ for (let j = 0; j < 3; j++) {
404404+ colors[(i + j) * 3] = newColor.r;
405405+ colors[(i + j) * 3 + 1] = newColor.g;
406406+ colors[(i + j) * 3 + 2] = newColor.b;
407407+ }
408408+ }
303409 }
304410305411 oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute(
+291
src/worlds/helper/octree.ts
···11+import {
22+ Vector3,
33+ Box3,
44+ Material,
55+ Object3D,
66+ Mesh,
77+ BoxGeometry,
88+ MeshStandardMaterial,
99+ BufferAttribute,
1010+ BufferGeometry,
1111+ Points,
1212+ PointsMaterial,
1313+} from "three";
1414+1515+export type OctreeOptions = {
1616+ bounds?: Box3;
1717+1818+ size?: number;
1919+2020+ min?: Vector3;
2121+ max?: Vector3;
2222+2323+ points?: Vector3[];
2424+2525+ capacity?: number;
2626+};
2727+2828+export type Vector3Data = Vector3 & { data?: unknown };
2929+3030+export class Octree {
3131+ boundary: Box3;
3232+3333+ points: Vector3[];
3434+3535+ capacity: number;
3636+3737+ subdivisions: Octree[] | undefined = undefined;
3838+3939+ constructor(opts: OctreeOptions = {}) {
4040+ opts ??= {};
4141+4242+ this.points = [];
4343+4444+ if (opts.bounds) {
4545+ this.boundary = opts.bounds.clone();
4646+ } else if (opts.size) {
4747+ const s = opts.size;
4848+ this.boundary = new Box3(new Vector3(-s, -s, -s), new Vector3(s, s, s));
4949+ } else if (opts.min || opts.max) {
5050+ const min = opts.min || new Vector3(-1, -1, -1);
5151+ const max = opts.max || new Vector3(1, 1, 1);
5252+ this.boundary = new Box3(min, max);
5353+ } else if (opts.points && opts.points.length > 0) {
5454+ const min = opts.points[0].clone();
5555+ const max = opts.points[0].clone();
5656+ for (const p of opts.points) {
5757+ min.x = Math.min(min.x, p.x);
5858+ min.y = Math.min(min.y, p.y);
5959+ min.z = Math.min(min.z, p.z);
6060+6161+ max.x = Math.max(max.x, p.x);
6262+ max.y = Math.max(max.y, p.y);
6363+ max.z = Math.max(max.z, p.z);
6464+ }
6565+ this.boundary = new Box3(min, max);
6666+ } else {
6767+ this.boundary = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));
6868+ }
6969+7070+ this.capacity = opts.capacity || 4;
7171+7272+ if (opts.points) {
7373+ for (const p of opts.points) {
7474+ this.insertXYZ(p.x, p.y, p.z);
7575+ }
7676+ }
7777+ }
7878+7979+ subdivide() {
8080+ // if already subdivided exit silently
8181+ if (this.subdivisions != undefined) return;
8282+8383+ // divide each dimension => 2 * 2 * 2 = 8 subdivisions
8484+ const size = new Vector3();
8585+ const subdivisions: Octree[] = [];
8686+ for (let x = 0; x < 2; x++) {
8787+ for (let y = 0; y < 2; y++) {
8888+ for (let z = 0; z < 2; z++) {
8989+ const min = this.boundary.min.clone();
9090+ const max = this.boundary.max.clone();
9191+ this.boundary.getSize(size);
9292+ size.divideScalar(2);
9393+9494+ min.x += x * size.x;
9595+ min.y += y * size.y;
9696+ min.z += z * size.z;
9797+ max.x -= (1 - x) * size.x;
9898+ max.y -= (1 - y) * size.y;
9999+ max.z -= (1 - z) * size.z;
100100+101101+ subdivisions.push(
102102+ new Octree({
103103+ min: min,
104104+ max: max,
105105+ capacity: this.capacity,
106106+ }),
107107+ );
108108+ }
109109+ }
110110+ }
111111+ this.subdivisions = subdivisions;
112112+ }
113113+114114+ // returns array of points where
115115+ // distance between pos and point is less than dist
116116+ query(pos: Vector3Data, dist = 1) {
117117+ const points = this.queryXYZ(pos.x, pos.y, pos.z, dist);
118118+ for (let i = points.length - 1; i >= 0; i--) {
119119+ if (points[i].distanceTo(pos) > dist) points.splice(i, 1);
120120+ }
121121+ return points;
122122+ }
123123+124124+ // vector3 free version, returns points in box around xyz
125125+ queryXYZ(x: number, y: number, z: number, s: number) {
126126+ const min = new Vector3(x - s, y - s, z - s),
127127+ max = new Vector3(x + s, y + s, z + s);
128128+ const box = new Box3(min, max);
129129+130130+ return this.queryBox(box);
131131+ }
132132+133133+ queryBox(box: Box3, found: Vector3Data[] = []) {
134134+ found ??= [];
135135+136136+ if (!box.intersectsBox(this.boundary)) return found;
137137+138138+ for (const p of this.points) {
139139+ if (box.containsPoint(p)) found.push(p);
140140+ }
141141+ if (this.subdivisions) {
142142+ for (const sub of this.subdivisions) {
143143+ sub.queryBox(box, found);
144144+ }
145145+ }
146146+ return found;
147147+ }
148148+149149+ // returns true if no points are closer than dist to point
150150+ minDist(pos: Vector3, dist: number) {
151151+ return this.query(pos, dist).length < 1;
152152+ }
153153+154154+ // insert point with optional data (sets vec.data = data)
155155+ insert(pos: Vector3Data, data: unknown = undefined) {
156156+ return this.insertPoint(pos, data);
157157+ }
158158+159159+ // vector3 free version
160160+ insertXYZ(x: number, y: number, z: number, data: unknown = undefined) {
161161+ return this.insertPoint(new Vector3(x, y, z), data);
162162+ }
163163+164164+ insertPoint(p: Vector3, data: unknown = undefined) {
165165+ p = p.clone();
166166+167167+ // @ts-expect-error - data is not a property of Vector3
168168+ if (data) p.data = data;
169169+170170+ if (!this.boundary.containsPoint(p)) return false;
171171+172172+ if (this.points.length < this.capacity) {
173173+ this.points.push(p);
174174+ return true;
175175+ } else {
176176+ this.subdivide();
177177+ let added = false;
178178+ for (const sub of this.subdivisions ?? []) {
179179+ if (sub.insertPoint(p, data)) added = true;
180180+ }
181181+ return added;
182182+ }
183183+ }
184184+185185+ showBoxes(mat: Material, parent: Object3D | undefined = undefined) {
186186+ const size = new Vector3();
187187+ this.boundary.getSize(size);
188188+189189+ const box = new BoxGeometry(size.x * 2, size.y * 2, size.z * 2);
190190+ const mesh = new Mesh(
191191+ box,
192192+ mat ||
193193+ new MeshStandardMaterial({
194194+ wireframe: true,
195195+ }),
196196+ );
197197+ this.boundary.getCenter(mesh.position);
198198+199199+ parent ??= new Object3D();
200200+ parent.add(mesh);
201201+202202+ if (this.subdivisions) {
203203+ for (const sub of this.subdivisions) sub.showBoxes(mat, parent);
204204+ }
205205+ return parent;
206206+ }
207207+208208+ show(
209209+ opts: {
210210+ pointsOnly?: boolean;
211211+ mat?: Material;
212212+ size?: number;
213213+ sizeAttenuation?: boolean;
214214+ p?: Vector3;
215215+ min?: number;
216216+ } = {},
217217+ ) {
218218+ opts ??= {};
219219+220220+ const pointsOnly = opts.pointsOnly;
221221+ let mat = opts.mat;
222222+ const points = this.all();
223223+224224+ const pointsGeo = new BufferGeometry();
225225+ const positionData = new Float32Array(points.length * 3);
226226+ const colorData = new Float32Array(points.length * 3);
227227+228228+ let q;
229229+230230+ if (opts.p && opts.min) {
231231+ for (const point of points) {
232232+ // @ts-expect-error - close is not a property of Vector3
233233+ point.close = false;
234234+ }
235235+ q = this.query(opts.p, opts.min);
236236+237237+ for (const point of q) {
238238+ // @ts-expect-error - close is not a property of Vector3
239239+ point.close = true;
240240+ }
241241+ }
242242+243243+ for (let i = 0; i < points.length; i++) {
244244+ positionData[i * 3] = points[i].x;
245245+ positionData[i * 3 + 1] = points[i].y;
246246+ positionData[i * 3 + 2] = points[i].z;
247247+248248+ // @ts-expect-error - close is not a property of Vector3
249249+ colorData[i * 3] = points[i].close ? 1 : 0.7;
250250+251251+ // @ts-expect-error - close is not a property of Vector3
252252+ colorData[i * 3 + 1] = points[i].close ? 0 : 0.7;
253253+254254+ // @ts-expect-error - close is not a property of Vector3
255255+ colorData[i * 3 + 2] = points[i].close ? 0 : 0.7;
256256+ }
257257+ pointsGeo.setAttribute("position", new BufferAttribute(positionData, 3));
258258+ pointsGeo.setAttribute("color", new BufferAttribute(colorData, 3));
259259+ const pointMesh = new Points(
260260+ pointsGeo,
261261+ new PointsMaterial({
262262+ size: opts.size || 1,
263263+ sizeAttenuation: opts.sizeAttenuation || false,
264264+ vertexColors: true,
265265+ }),
266266+ );
267267+ if (pointsOnly) return pointMesh;
268268+269269+ mat =
270270+ mat ||
271271+ new MeshStandardMaterial({
272272+ transparent: true,
273273+ opacity: 0.01,
274274+ depthTest: false,
275275+ });
276276+ const boxes = this.showBoxes(mat);
277277+ boxes.add(pointMesh);
278278+ return boxes;
279279+ }
280280+281281+ all(arr: Vector3Data[] = []) {
282282+ arr ??= [];
283283+ for (const p of this.points) {
284284+ arr.push(p);
285285+ }
286286+ if (this.subdivisions) {
287287+ for (const subs of this.subdivisions) subs.all(arr);
288288+ }
289289+ return arr;
290290+ }
291291+}
-275
src/worlds/helper/octtree.ts
···11-import * as THREE from 'three';
22-33-export type OcttreeOptions = {
44- bounds?: THREE.Box3;
55-66- size?: number;
77-88- min?: THREE.Vector3;
99- max?: THREE.Vector3;
1010-1111- points?: THREE.Vector3[];
1212-1313- capacity?: number;
1414-};
1515-1616-export class Octtree {
1717- boundary: THREE.Box3;
1818-1919- points: THREE.Vector3[];
2020-2121- capacity: number;
2222-2323- subdivisions: Octtree[] | undefined = undefined;
2424-2525- constructor(opts: OcttreeOptions = {}) {
2626- opts ??= {};
2727-2828- this.points = [];
2929-3030- if (opts.bounds) {
3131- this.boundary = opts.bounds.clone();
3232- } else if (opts.size) {
3333- const s = opts.size;
3434- this.boundary = new THREE.Box3(new THREE.Vector3(-s, -s, -s), new THREE.Vector3(s, s, s));
3535- } else if (opts.min || opts.max) {
3636- const min = opts.min || new THREE.Vector3(-1, -1, -1);
3737- const max = opts.max || new THREE.Vector3(1, 1, 1);
3838- this.boundary = new THREE.Box3(min, max);
3939- } else if (opts.points && opts.points.length > 0) {
4040- const min = opts.points[0].clone();
4141- const max = opts.points[0].clone();
4242- for (const p of opts.points) {
4343- min.x = Math.min(min.x, p.x);
4444- min.y = Math.min(min.y, p.y);
4545- min.z = Math.min(min.z, p.z);
4646-4747- max.x = Math.max(max.x, p.x);
4848- max.y = Math.max(max.y, p.y);
4949- max.z = Math.max(max.z, p.z);
5050- }
5151- this.boundary = new THREE.Box3(min, max);
5252- } else {
5353- this.boundary = new THREE.Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1));
5454- }
5555-5656- this.capacity = opts.capacity || 4;
5757-5858- if (opts.points) {
5959- for (const p of opts.points) {
6060- this.insertXYZ(p.x, p.y, p.z);
6161- }
6262- }
6363- }
6464-6565- subdivide() {
6666- // if already subdivided exit silently
6767- if (this.subdivisions != undefined) return;
6868-6969- // divide each dimension => 2 * 2 * 2 = 8 subdivisions
7070- const size = new THREE.Vector3();
7171- const subdivisions: Octtree[] = [];
7272- for (let x = 0; x < 2; x++) {
7373- for (let y = 0; y < 2; y++) {
7474- for (let z = 0; z < 2; z++) {
7575- const min = this.boundary.min.clone();
7676- const max = this.boundary.max.clone();
7777- this.boundary.getSize(size);
7878- size.divideScalar(2);
7979-8080- min.x += x * size.x;
8181- min.y += y * size.y;
8282- min.z += z * size.z;
8383- max.x -= (1 - x) * size.x;
8484- max.y -= (1 - y) * size.y;
8585- max.z -= (1 - z) * size.z;
8686-8787- subdivisions.push(
8888- new Octtree({
8989- min: min,
9090- max: max,
9191- capacity: this.capacity
9292- })
9393- );
9494- }
9595- }
9696- }
9797- this.subdivisions = subdivisions;
9898- }
9999-100100- // returns array of points where
101101- // distance between pos and point is less than dist
102102- query(pos: THREE.Vector3, dist = 1) {
103103- const points = this.queryXYZ(pos.x, pos.y, pos.z, dist);
104104- for (let i = points.length - 1; i >= 0; i--) {
105105- if (points[i].distanceTo(pos) > dist) points.splice(i, 1);
106106- }
107107- return points;
108108- }
109109-110110- // vector3 free version, returns points in box around xyz
111111- queryXYZ(x: number, y: number, z: number, s: number) {
112112- const min = new THREE.Vector3(x - s, y - s, z - s),
113113- max = new THREE.Vector3(x + s, y + s, z + s);
114114- const box = new THREE.Box3(min, max);
115115-116116- return this.queryBox(box);
117117- }
118118-119119- queryBox(box: THREE.Box3, found: THREE.Vector3[] = []) {
120120- found ??= [];
121121-122122- if (!box.intersectsBox(this.boundary)) return found;
123123-124124- for (const p of this.points) {
125125- if (box.containsPoint(p)) found.push(p);
126126- }
127127- if (this.subdivisions) {
128128- for (const sub of this.subdivisions) {
129129- sub.queryBox(box, found);
130130- }
131131- }
132132- return found;
133133- }
134134-135135- // returns true if no points are closer than dist to point
136136- minDist(pos: THREE.Vector3, dist: number) {
137137- return this.query(pos, dist).length < 1;
138138- }
139139-140140- // insert point with optional data (sets vec.data = data)
141141- insert(pos: THREE.Vector3, data: unknown = undefined) {
142142- return this.insertPoint(pos, data);
143143- }
144144- // vector3 free version
145145- insertXYZ(x: number, y: number, z: number, data: unknown = undefined) {
146146- return this.insertPoint(new THREE.Vector3(x, y, z), data);
147147- }
148148- insertPoint(p: THREE.Vector3, data: unknown = undefined) {
149149- p = p.clone();
150150-151151- // @ts-expect-error - data is not a property of Vector3
152152- if (data) p.data = data;
153153-154154- if (!this.boundary.containsPoint(p)) return false;
155155-156156- if (this.points.length < this.capacity) {
157157- this.points.push(p);
158158- return true;
159159- } else {
160160- this.subdivide();
161161- let added = false;
162162- for (const sub of this.subdivisions ?? []) {
163163- if (sub.insertPoint(p, data)) added = true;
164164- }
165165- return added;
166166- }
167167- }
168168-169169- showBoxes(mat: THREE.Material, parent: THREE.Object3D | undefined = undefined) {
170170- const size = new THREE.Vector3();
171171- this.boundary.getSize(size);
172172-173173- const box = new THREE.BoxGeometry(size.x * 2, size.y * 2, size.z * 2);
174174- const mesh = new THREE.Mesh(
175175- box,
176176- mat ||
177177- new THREE.MeshStandardMaterial({
178178- wireframe: true
179179- })
180180- );
181181- this.boundary.getCenter(mesh.position);
182182-183183- parent ??= new THREE.Object3D();
184184- parent.add(mesh);
185185-186186- if (this.subdivisions) {
187187- for (const sub of this.subdivisions) sub.showBoxes(mat, parent);
188188- }
189189- return parent;
190190- }
191191-192192- show(
193193- opts: {
194194- pointsOnly?: boolean;
195195- mat?: THREE.Material;
196196- size?: number;
197197- sizeAttenuation?: boolean;
198198- p?: THREE.Vector3;
199199- min?: number;
200200- } = {}
201201- ) {
202202- opts ??= {};
203203-204204- const pointsOnly = opts.pointsOnly;
205205- let mat = opts.mat;
206206- const points = this.all();
207207-208208- const pointsGeo = new THREE.BufferGeometry();
209209- const positionData = new Float32Array(points.length * 3);
210210- const colorData = new Float32Array(points.length * 3);
211211-212212- let q;
213213-214214- if (opts.p && opts.min) {
215215- for (const point of points) {
216216- // @ts-expect-error - close is not a property of Vector3
217217- point.close = false;
218218- }
219219- q = this.query(opts.p, opts.min);
220220-221221- for (const point of q) {
222222- // @ts-expect-error - close is not a property of Vector3
223223- point.close = true;
224224- }
225225- }
226226-227227- for (let i = 0; i < points.length; i++) {
228228- positionData[i * 3] = points[i].x;
229229- positionData[i * 3 + 1] = points[i].y;
230230- positionData[i * 3 + 2] = points[i].z;
231231-232232- // @ts-expect-error - close is not a property of Vector3
233233- colorData[i * 3] = points[i].close ? 1 : 0.7;
234234-235235- // @ts-expect-error - close is not a property of Vector3
236236- colorData[i * 3 + 1] = points[i].close ? 0 : 0.7;
237237-238238- // @ts-expect-error - close is not a property of Vector3
239239- colorData[i * 3 + 2] = points[i].close ? 0 : 0.7;
240240- }
241241- pointsGeo.setAttribute('position', new THREE.BufferAttribute(positionData, 3));
242242- pointsGeo.setAttribute('color', new THREE.BufferAttribute(colorData, 3));
243243- const pointMesh = new THREE.Points(
244244- pointsGeo,
245245- new THREE.PointsMaterial({
246246- size: opts.size || 1,
247247- sizeAttenuation: opts.sizeAttenuation || false,
248248- vertexColors: true
249249- })
250250- );
251251- if (pointsOnly) return pointMesh;
252252-253253- mat =
254254- mat ||
255255- new THREE.MeshStandardMaterial({
256256- transparent: true,
257257- opacity: 0.01,
258258- depthTest: false
259259- });
260260- const boxes = this.showBoxes(mat);
261261- boxes.add(pointMesh);
262262- return boxes;
263263- }
264264-265265- all(arr: THREE.Vector3[] = []) {
266266- arr ??= [];
267267- for (const p of this.points) {
268268- arr.push(p);
269269- }
270270- if (this.subdivisions) {
271271- for (const subs of this.subdivisions) subs.all(arr);
272272- }
273273- return arr;
274274- }
275275-}
+73-66
src/worlds/materials/AtmosphereMaterial.ts
···11import { ShaderMaterial, Vector3 } from "three";
2233-const atmosphereShader = {
44- uniforms: {
55- lightDirection: { value: new Vector3(1.0, 1.0, 0.0).normalize() }, // Sunlight direction
66- atmosphereColor: { value: new Vector3(0.3, 0.6, 1.0) }, // Blue tint for atmosphere
77- },
88- vertexShader: `
99-varying vec3 vNormal;
1010-varying vec3 vViewLightDirection; // Light direction relative to the camera
1111-varying vec3 vViewPosition; // View position
33+export function createAtmosphereMaterial(
44+ color: Vector3 | undefined = undefined,
55+ lightDirection: Vector3 | undefined = undefined,
66+) {
77+ const uniforms = {
88+ lightDirection: {
99+ value: (lightDirection ?? new Vector3(1.0, 1.0, 0.0)).normalize(),
1010+ },
1111+ atmosphereColor: { value: color ?? new Vector3(0.3, 0.6, 1.0) },
1212+ };
12131313-uniform vec3 lightDirection; // Light direction in world space
1414+ const atmosphereShader = {
1515+ uniforms,
1616+ vertexShader: `
1717+ varying vec3 vNormal;
1818+ varying vec3 vViewLightDirection; // Light direction relative to the camera
1919+ varying vec3 vViewPosition; // View position
2020+2121+ uniform vec3 lightDirection; // Light direction in world space
2222+2323+ void main() {
2424+ // Transform the vertex normal to world space
2525+ vNormal = normalize(normalMatrix * normal);
2626+ // Transform the light direction to view space
2727+ vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz;
2828+ vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
2929+3030+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
3131+ }
3232+ `,
3333+ fragmentShader: `
3434+ varying vec3 vNormal;
3535+ varying vec3 vViewLightDirection;
3636+ varying vec3 vViewPosition;
3737+ uniform vec3 atmosphereColor;
3838+3939+ void main() {
4040+4141+ // Normalize the normal and the view direction
4242+ vec3 viewDirection = normalize(vViewPosition);
4343+4444+ // Calculate how much of the surface is perpendicular to the view direction
4545+ float viewFactor = dot(normalize(vNormal), -viewDirection);
4646+4747+ // Calculate the dot product of the light direction and the surface normal
4848+ float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection));
4949+5050+ // Use smoothstep to soften the transition from light to dark side
5151+ lightFactor = smoothstep(-0.2, 0.4, lightFactor);
5252+5353+ // Ensure a minimum glow, even on the dark side
5454+ float minGlow = 0.2; // Adjust this to control how much it glows on the dark side
5555+ lightFactor = mix(minGlow, 1.0, lightFactor);
5656+5757+ // Adjust the intensity for the atmosphere's glow, including the minimum glow
5858+ float dotProduct = dot(normalize(vNormal), vec3(0, 0.0, 1.0));
5959+ float intensity = pow(dotProduct, 8.0);
6060+ intensity *= lightFactor;
6161+6262+ viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0);
6363+6464+ // Use smoothstep for a smooth transition on the edges
6565+ float atmosphereFactor = smoothstep(0.2, 0.6, viewFactor);
6666+6767+ // Output the final color
6868+ gl_FragColor = vec4(atmosphereColor, intensity * atmosphereFactor);
6969+ // Set the final fragment color with the computed intensity
7070+ //gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity;
7171+ }`,
7272+ transparent: true,
7373+ depthWrite: false,
7474+ };
14751515-void main() {
1616- // Transform the vertex normal to world space
1717- vNormal = normalize(normalMatrix * normal);
1818- // Transform the light direction to view space
1919- vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz;
2020- vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
2121-2222- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
2323-}
2424-`,
2525- fragmentShader: `
2626- varying vec3 vNormal;
2727- varying vec3 vViewLightDirection;
2828- varying vec3 vViewPosition;
2929- uniform vec3 atmosphereColor;
3030-3131- void main() {
3232-3333- // Normalize the normal and the view direction
3434- vec3 viewDirection = normalize(vViewPosition);
3535-3636- // Calculate how much of the surface is perpendicular to the view direction
3737- float viewFactor = dot(normalize(vNormal), -viewDirection);
3838-3939- // Calculate the dot product of the light direction and the surface normal
4040- float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection));
4141-4242- // Use smoothstep to soften the transition from light to dark side
4343- lightFactor = smoothstep(-0.2, 0.4, lightFactor);
4444-4545- // Ensure a minimum glow, even on the dark side
4646- float minGlow = 0.2; // Adjust this to control how much it glows on the dark side
4747- lightFactor = mix(minGlow, 1.0, lightFactor);
4848-4949- // Adjust the intensity for the atmosphere's glow, including the minimum glow
5050- float dotProduct = dot(normalize(vNormal), vec3(0, 0.0, 1.0));
5151- float intensity = pow(dotProduct, 8.0);
5252- intensity *= lightFactor;
5353-5454- viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0);
5555-5656- // Use smoothstep for a smooth transition on the edges
5757- float atmosphereFactor = smoothstep(0.2, 0.6, viewFactor);
5858-5959- // Output the final color
6060- gl_FragColor = vec4(atmosphereColor, intensity * atmosphereFactor);
6161- // Set the final fragment color with the computed intensity
6262- //gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity;
6363- }`,
6464- transparent: true,
6565- depthWrite: false,
6666-};
6767-6868-const atmosphereMaterial = new ShaderMaterial(atmosphereShader);
6969-7070-export default atmosphereMaterial;
7676+ return new ShaderMaterial(atmosphereShader);
7777+}