Updated 3D Environment for Library
Reading DEV Articles
A lot of you wonderful people enjoyed my last post on Oni, so heres an update on what I've been working on.
I've been spending the last few days building the DEV Library inside Oniria, and somewhere along the way the project stopped feeling like "a cool Three.js scene" and started feeling like I was building my own tiny game-engine workflow.
The idea behind the library is pretty simple:
DEV articles become physical books.
Instead of opening DEV.to and scrolling through cards, you walk through rooms, browse shelves, pull out an article, read it, and return to the exact place you found it.
But actually constructing that space turned out to be one of the most interesting problems I've hit so far.
Earlier versions of Oniria leaned much harder into a cyber-world aesthetic: glowing structures, highways, towering architecture, and DEV articles scattered through something closer to a futuristic city. It looked interesting, but the more I built it, the more I realized the environment wasn't helping me understand the information inside it.
Articles were technically spatial, but the space itself didn't explain why anything was where it was. Eventually I started asking a simpler question: if thousands of articles were going to become physical objects, what kind of place would naturally organize them? The answer was obvious — a library. That shift changed the project from decorating a cyber-world with content into designing an actual information architecture: rooms became categories, shelves became collections, books became articles, and the building itself could communicate how DEV is organized.
That decision also created a completely different engineering problem. I was no longer placing a handful of futuristic landmarks; I was trying to build an entire navigable library — rooms, shelves, paths, furniture, signs, lighting, and hundreds of precise spatial relationships — almost entirely through Three.js code, without a traditional live level editor.
That progression looked like this:
cyber city
↓
looked cool, but lacked informational meaning
↓
"what spatial metaphor actually fits articles?"
↓
library
↓
rooms = information architecture
↓
now we need to physically build it
↓
wait... we don't have a level editor
↓
build our own tools
The problem: I wasn't using a level editor
Before building the in-world authoring tools, most object placement happened directly in Three.js. All by code. No 3D Editor. Ha, take that, purist!
I wrote a small helper that clones one of the loaded GLB assets, then applies its world position, scale, and rotation:
const placeAsset = (
template: THREE.Group,
x: number,
y: number,
z: number,
scale = 1,
rotationY = 0,
rotationX = 0,
) => {
const instance = template.clone(true)
instance.position.x += x
instance.position.y += y
instance.position.z += z
instance.scale.multiplyScalar(scale)
instance.rotation.y += rotationY
instance.rotation.x += rotationX
group.add(instance)
return instance
}
That made placing something fairly straightforward:
const table = placeAsset(
readingTable,
-16.2, // x
0.05, // y
-31.8, // z
1, // scale
Math.PI / 2,
)
But moving it still meant editing numbers:
table.position.x += 0.8
table.position.z -= 1.2
table.rotation.y += Math.PI / 4
Or changing the original call:
const table = placeAsset(
readingTable,
-15.4,
0.05,
-33.0,
1,
Math.PI * 0.75,
)
Then:
save
↓
reload
↓
walk back across the library
↓
look at it
↓
"nope"
↓
change the numbers again
That was basically my level editor. 😭 Below is an actual snippet of what I was working with:
And once the environment grew beyond a handful of props, it became painfully obvious that this wasn't going to scale.
With a normal interface, I can change some CSS, refresh the browser, and immediately see whether a card needs another 16px of margin.
In a 3D world, changing:
position: [-16.2, 0, -31.8]
might mean walking across an entire room just to discover that a table is clipping through a bookshelf.
And at this point the library wasn't just a few props.
I was placing:
- walls and architectural pieces
- real and decorative bookshelves
- tables and chairs
- rugs
- plants
- pavilions
- signs
- room entrances
- shelf rows
- collision boundaries
- lights
- eventually, empty spaces reserved for shelves that don't even exist yet
The first version of my workflow was basically:
change coordinates
↓
save
↓
reload the world
↓
walk to the room
↓
inspect the object
↓
realize it's wrong
↓
repeat
It worked.
It was also slowly driving me insane.
The obvious answer would have been to move the whole project into a traditional level editor.
But I didn't really want another application to become the source of truth for the library.
The architecture was already deeply connected to code:
DEV data
↓
district
↓
room
↓
shelf slot
↓
Three.js placement
Also, the Three.js editor was really freaking annoying to use.
At this point I realized that moving assets this way would either take forever or it would never be perfect to my liking. There had to be an easier way — and then I had an idea. Why don't we make the location of each shelf predetermined by a layout I create? Essentially a blueprint — then have Codex place the individual assets at each coord/pin that I place. So that became my next goal.
I needed to see coordinates inside the world
So instead I started building a tiny authoring system directly into Oniria.
The basic idea was simple:
If I'm standing somewhere in the world and think, "this object should go here," I should be able to record it here.
I added an in-world layout marker tool.
Now I could walk into a room, find the location visually, and drop a pin.
A marker could store something like:
{
"label": "R1-P01",
"roomSlot": 1,
"districtId": "latest",
"x": 16.154,
"y": 0,
"z": 5.369,
"yaw": 1.57
}
Suddenly I wasn't trying to mentally convert a 3D room into numbers.
I could use the room itself to produce the numbers.
The workflow became:
walk through the space
↓
find the location visually
↓
drop a marker
↓
capture position + rotation
↓
turn the marker into real architecture
That sounds like a small change.
It completely changed how I could build the library.
The world became my editor
For example, when I wanted to add an object to a particular room, I could physically stand where it belonged and create a marker.
Then that surveyed position could go directly back into the scene:
const surveyedPlacement = {
x: 16.154,
y: 0,
z: 5.369,
yaw: Math.PI / 2,
}
const instance = placeAsset(
someLibraryAsset,
surveyedPlacement.x,
surveyedPlacement.y,
surveyedPlacement.z,
1,
surveyedPlacement.yaw,
)
I started using the same workflow for things like:
"This shelf belongs here."
"This pavilion needs to move farther from the path."
"These two signs are colliding."
"This light needs to sit over this exact area."
"This decorative bookcase should sit between these DEV shelves."
Persisting the layout
I also didn't want those markers to disappear every time the browser refreshed. So the markers became Sanity documents.
A marker now has a persistent identity:
type LibraryLayoutMarker = {
id: string
label: string
roomSlot: number
districtId?: string
x: number
y: number
z: number
yaw?: number
width?: number
depth?: number
}
That means a layout session can survive reloads.
I can place several markers, come back later, and continue working from the same spatial notes.
I can also export them.
For example:
{
"format": "oniria-library-layout-pins-v1",
"markers": [
{
"label": "R1-P01",
"roomSlot": 1,
"x": 16.154,
"y": 0,
"z": 5.369
}
]
}
At this point I had accidentally built something halfway between a debugging tool, a survey tool, and a very small level editor.
And it solved a real problem immediately. Yay!
What started as a frustrating layout problem ended up teaching me something bigger about the project: if Oniria is going to treat the web as a place instead of a page, then the tools for building that place matter just as much as the content inside it. The DEV Library now has its own spatial logic, persistent layout markers, searchable shelves, dynamic slots, and an architecture that can evolve without losing its sense of place. I'm still building it room by room, but that's also the part I've started to enjoy most — every time the world gets difficult to work with, I end up building a new piece of the system that makes Oniria feel a little more real.
















!["[260925] 뉴스 수집 범위 실시간화와 보조 카드 안전장치 배포"](https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fibmjiil2r7ftj4ahqly1.png)

!["[260922~260927] 안전장치를 겹겹이 쌓고, 전제를 다시 검증한 한 주"](https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2wh1c9v3z72p19jqfxn.png)
!["[260928] R9700 백업 GPU 자가치료 배선과 동시요청 상향 재검토"](https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wsam6i6svuvawg5s5do.png)