Files
NexusDashboard/app/templates/ldd/ldd.html.j2
Aaron Kimbrell 18bab8d57a fix: place player-built models using their stored position and rotation
DarkflameServer now normalizes UGC model LXFML around the model's own
origin and stores the model's world position and rotation in
properties_contents, the same as for prebuilt models. The property
viewer only applied that transform to prebuilt models, so every
player-built model rendered piled up at the origin.

Pass each UGC model's stored position and rotation to the LDD converter
and apply it to every brick (including flex parts) when building the
scene.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 19:39:36 -05:00

1203 lines
37 KiB
Django/Jinja

<!DOCTYPE html>
<html lang="en">
<head>
<title>LDD Webviewer</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='lddviewer/main.css')}}">
<style>
body {
color: #444;
}
a {
color: #08f;
}
</style>
</head>
<body>
<div id="info">
LDD (lxfml) Webviewer
<a href="https://github.com/sttng/LDD-Webviewer">
Credit to sttng
</a>
<br/>
{% if (property_data and current_user.gm_level >= 3) %}
<a role="button" class="btn text-{% if property_data.mod_approved %}danger{% else %}success{% endif %} btn-block"
href='{{url_for('properties.approve', id=property_data.id)}}'>
{% if property_data.mod_approved %} Unapprove {% else %} Approve {% endif %}
</a>
{% endif %}
</div>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.116.0/build/three.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.116.0/examples/js/controls/OrbitControls.js"></script>
<script type="text/javascript" src="{{ url_for('static', filename='lddviewer/base64-binary.js') }}"></script>
<script type='module'>
import {MTLLoader} from 'https://cdn.jsdelivr.net/npm/three@0.116.0/examples/jsm/loaders/MTLLoader.js'
import {OBJLoader} from 'https://cdn.jsdelivr.net/npm/three@0.116.0/examples/jsm/loaders/OBJLoader.js'
//Three.js stuff
const scene = new THREE.Scene();
let cammatr = new THREE.Matrix4();
{% if center %}
let brick_pos = new THREE.Vector3{{center}};
{% else %}
let brick_pos = new THREE.Vector3();
{% endif %}
class Matrix3D{
//done
constructor(n11=1,n12=0,n13=0,n14=0,n21=0,n22=1,n23=0,n24=0,n31=0,n32=0,n33=1,n34=0,n41=0,n42=0,n43=0,n44=1){
this.n11 = n11
this.n12 = n12
this.n13 = n13
this.n14 = n14
this.n21 = n21
this.n22 = n22
this.n23 = n23
this.n24 = n24
this.n31 = n31
this.n32 = n32
this.n33 = n33
this.n34 = n34
this.n41 = n41
this.n42 = n42
this.n43 = n43
this.n44 = n44
}
toString(){
return `[${this.n11}, ${this.n12}, ${this.n13}, ${this.n14}, ${this.n21}, ${this.n22}, ${this.n23}, ${this.n24}, ${this.n31}, ${this.n32}, ${this.n33}, ${this.n34}, ${this.n41}, ${this.n42}, ${this.n43}, ${this.n44}]`
}
rotate(angle, axis){
let c = Math.cos(angle)
let s = Math.sin(angle)
let t = 1 - c
let tx = t * axis.x
let ty = t * axis.y
let tz = t * axis.z
let sx = s * axis.x
let sy = s * axis.y
let sz = s * axis.z
this.n11 = c + axis.x * tx
this.n12 = axis.y * tx + sz
this.n13 = axis.z * tx - sy
this.n14 = 0
this.n21 = axis.x * ty - sz
this.n22 = c + axis.y * ty
this.n23 = axis.z * ty + sx
this.n24 = 0
this.n31 = axis.x * tz + sy
this.n32 = axis.y * tz - sx
this.n33 = c + axis.z * tz
this.n34 = 0
this.n41 = 0
this.n42 = 0
this.n43 = 0
this.n44 = 1
}
mul(other){
return new Matrix3D(this.n11 * other.n11 + this.n21 * other.n12 + this.n31 * other.n13 + this.n41 * other.n14,
this.n12 * other.n11 + this.n22 * other.n12 + this.n32 * other.n13 + this.n42 * other.n14,
this.n13 * other.n11 + this.n23 * other.n12 + this.n33 * other.n13 + this.n43 * other.n14,
this.n14 * other.n11 + this.n24 * other.n12 + this.n34 * other.n13 + this.n44 * other.n14,
this.n11 * other.n21 + this.n21 * other.n22 + this.n31 * other.n23 + this.n41 * other.n24,
this.n12 * other.n21 + this.n22 * other.n22 + this.n32 * other.n23 + this.n42 * other.n24,
this.n13 * other.n21 + this.n23 * other.n22 + this.n33 * other.n23 + this.n43 * other.n24,
this.n14 * other.n21 + this.n24 * other.n22 + this.n34 * other.n23 + this.n44 * other.n24,
this.n11 * other.n31 + this.n21 * other.n32 + this.n31 * other.n33 + this.n41 * other.n34,
this.n12 * other.n31 + this.n22 * other.n32 + this.n32 * other.n33 + this.n42 * other.n34,
this.n13 * other.n31 + this.n23 * other.n32 + this.n33 * other.n33 + this.n43 * other.n34,
this.n14 * other.n31 + this.n24 * other.n32 + this.n34 * other.n33 + this.n44 * other.n34,
this.n11 * other.n41 + this.n21 * other.n42 + this.n31 * other.n43 + this.n41 * other.n44,
this.n12 * other.n41 + this.n22 * other.n42 + this.n32 * other.n43 + this.n42 * other.n44,
this.n13 * other.n41 + this.n23 * other.n42 + this.n33 * other.n43 + this.n43 * other.n44,
this.n14 * other.n41 + this.n24 * other.n42 + this.n34 * other.n43 + this.n44 * other.n44)
}
}
class Point3D{
//done
constructor(x=0,y=0,z=0){
this.x = x
this.y = y
this.z = z
}
toString(){
return `[${this.x}, ${this.y}, ${this.z}]`
}
transformW(matrix){
let x = matrix.n11 * this.x + matrix.n21 * this.y + matrix.n31 * this.z
let y = matrix.n12 * this.x + matrix.n22 * this.y + matrix.n32 * this.z
let z = matrix.n13 * this.x + matrix.n23 * this.y + matrix.n33 * this.z
this.x = x
this.y = y
this.z = z
}
transform(matrix){
let x = matrix.n11 * this.x + matrix.n21 * this.y + matrix.n31 * this.z + matrix.n41
let y = matrix.n12 * this.x + matrix.n22 * this.y + matrix.n32 * this.z + matrix.n42
let z = matrix.n13 * this.x + matrix.n23 * this.y + matrix.n33 * this.z + matrix.n43
this.x = x
this.y = y
this.z = z
}
copy(){
return new Point3D(this.x, this.y, this.z)
}
}
class Point2D{
//done
constructor(x=0,y=0){
this.x = x
this.y = y
}
toString(){
return `[${this.x}, ${this.y}]`
}
copy(){
return new Point2D(this.x, this.y)
}
}
class Face{
//done
constructor(a=0,b=0,c=0){
this.a = a
this.b = b
this.c = c
}
toString(){
return `[${this.a}, ${this.b}, ${this.c}]`
}
}
class Group{
//done
constructor(node){
this.partRefs = node.getAttribute('partRefs').split(',')
}
}
class Bone{
//done
constructor(node){
this.refID = node.getAttribute('refID')
//console.log(node.getAttribute('transformation').split(',').map(parseFloat))
let [a, b, c, d, e, f, g, h, i, x, y, z] = node.getAttribute('transformation').split(',').map(parseFloat);
this.matrix = new Matrix3D(a,b,c,0,d,e,f,0,g,h,i,0,x,y,z,1);
//console.log(this.refID)
//console.log(this.matrix)
}
}
class Part{
//done
constructor(node){
this.isGrouped = false
this.GroupIDX = 0
this.Bones = []
this.refID = node.getAttribute('refID')
this.designID = node.getAttribute('designID')
this.materials = node.getAttribute('materials').split(',')
let lastm = '0'
for (const [i, m] of this.materials.entries()) {
if (m == '0'){
//this.materials[i] = lastm
this.materials[i] = this.materials[0]
}
else {
lastm = m
}
}
if (node.hasAttribute('decoration')){
this.decoration = node.getAttribute('decoration').split(',')
}
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Bone'){
this.Bones.push(new Bone(childnode))
}
}
}
}
class Brick{
//done
constructor(node){
this.refID = node.getAttribute('refID')
this.designID = node.getAttribute('designID')
this.Parts = []
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Part'){
this.Parts.push(new Part(childnode))
}
}
}
}
class Scene{
constructor(){
this.Bricks = []
this.Scenecamera = []
this.Groups = []
this.SceneBuildingInstructions = []
this.xmldata = ''
}
// placement: optional THREE.Matrix4 that places this model in the property. DLU stores player-built
// (UGC) models normalized around their own origin, with the position and rotation in properties_contents.
AddScene(file, placement = null){
let lxfmlfile = new DBURLFile(file,file)
this.xmldata = lxfmlfile.read()
if (this.xmldata === ""){
alert(this.xmldata)
}
let parser = new DOMParser();
let xml = parser.parseFromString(this.xmldata, "text/xml");
let nodes = xml.firstChild.childNodes;
for (let i = 0; i < nodes.length ;i++) {
let node = nodes[i]
if (node.nodeName == 'Meta'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'BrickSet'){
this.Version = childnode.getAttribute('version')
}
}
}
else if (node.nodeName == 'Bricks'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Brick'){
let brick = new Brick(childnode)
brick.placement = placement
this.Bricks.push(brick)
}
}
}
else if (node.nodeName == 'GroupSystems'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'GroupSystem'){
let subchildnodes = childnode.childNodes
for (let k = 0; k < subchildnodes.length ;k++) {
let subchildnode = subchildnodes[k]
if (subchildnode.nodeName == 'Group'){
this.Groups.push(new Group(subchildnode))
}
}
}
}
}
}
for (const [i, m] of this.Groups.entries()) {
for (const brick of this.Bricks){
for (const part of brick.Parts){
if (m.partRefs.indexOf(part.refID) !== -1) {
part.isGrouped = true
part.GroupIDX = i
}
}
}
}
console.log('Scene Loaded Brickversion: ' + this.Version)
}
}
class GeometryReader{
//done
constructor(data){
this.offset = 0
this.data = data
this.positions = []
this.normals = []
this.textures = []
this.faces = []
this.bonemap = []
this.texCount = 0
this.outpositions = []
this.outnormals = []
this.binary = "";
for (let i = 0; i < data.length; i++ ) {
this.binary += String.fromCharCode(data.charCodeAt(i) & 255)
}
this.data = Base64Binary.decodeArrayBuffer(btoa(this.binary));
this.view = new Uint8Array(this.data);
if (this.readInt() == 1111961649){
this.valueCount = this.readInt()
this.indexCount = this.readInt()
this.faceCount = this.indexCount / 3
let options = this.readInt()
for (let i = 0; i < this.valueCount ;i++) {
this.positions.push(new Point3D(this.readFloat(), this.readFloat(), this.readFloat()))
}
for (let i = 0; i < this.valueCount ;i++) {
this.normals.push(new Point3D(this.readFloat(), this.readFloat(), this.readFloat()))
}
if ((options & 3) == 3){
this.texCount = this.valueCount
for (let i = 0; i < this.valueCount ;i++) {
this.textures.push(new Point2D(this.readFloat(), this.readFloat()))
}
}
for (let i = 0; i < this.faceCount ;i++) {
this.faces.push(new Face(this.readInt(), this.readInt(), this.readInt()))
}
if ((options & 48) == 48){
let num = this.readInt()
this.offset += (num * 4) + (this.indexCount * 4)
num = this.readInt()
this.offset += (3 * num * 4) + (this.indexCount * 4)
}
let bonelength = this.readInt()
this.bonemap.length = this.valueCount
this.bonemap.fill(0);
if ((bonelength > this.valueCount) || (bonelength > this.faceCount)){
let datastart = this.offset
this.offset += bonelength
for (let i = 0; i < this.valueCount ;i++) {
let boneoffset = this.readInt() + 4
this.bonemap[i] = this.read_Int(datastart + boneoffset)
}
}
}
}
read_Int(_offset){
//console.log(_offset)
let ret = (this.view[_offset+0]) + (this.view[_offset+1]<<8)+ (this.view[_offset+2]<<16) + (this.view[_offset+3]<<24)
//console.log(ret)
return ret
}
readInt(){
//let ret = (this.view[this.offset+0]<<24) + (this.view[this.offset+1]<<16)+ (this.view[this.offset+2]<<8) + (this.view[this.offset+3])
let ret = (this.view[this.offset+0]) + (this.view[this.offset+1]<<8)+ (this.view[this.offset+2]<<16) + (this.view[this.offset+3]<<24)
this.offset += 4
//console.log(ret)
return ret
}
readFloat(){
let tempdata = [(this.view[this.offset+3]), (this.view[this.offset+2]), (this.view[this.offset+1]), (this.view[this.offset+0])];
// Create a buffer and a data view of it
let buf = new ArrayBuffer(4);
let view = new DataView(buf);
// set bytes
tempdata.forEach(function (b, i) {
view.setInt8(i, b);
});
// Read the bits as a float; note that by doing this, we're implicitly converting it from a 32-bit float into JavaScript's native 64-bit double
let number = view.getFloat32(0);
this.offset += 4
return number
}
}
class Geometry {
//done
constructor(designID, database){
this.designID = designID
this.Parts = []
this.studsFields2D = []
let lod = {{ lod }}
let GeometryLocation = `brickprimitives/lod${lod}/${designID}.g`
let PrimitiveLocation = `Primitives/${designID}.xml`
let GeometryCount = 0
while (GeometryLocation in database.filelist) {
this.Parts[GeometryCount] = new GeometryReader(database.filelist[GeometryLocation].read())
GeometryCount = GeometryCount + 1
GeometryLocation = `brickprimitives/lod${lod}/${designID}.g${GeometryCount}`
}
let primitive = new Primitive(database.filelist[PrimitiveLocation].read())
this.Partname = primitive.Designname
this.studsFields2D = primitive.Fields2D
//preflex
for (const [h, part] of this.Parts.entries()) {
//transform
for (const [i, b] of primitive.Bones.entries()) {
//positions
for (const [j, p] of this.Parts[h].positions.entries()) {
if (this.Parts[h].bonemap[j] == i){
this.Parts[h].positions[j].transform(b.matrix)
}
}
//normals
for (const [k, n] of this.Parts[h].normals.entries()) {
if (this.Parts[h].bonemap[k] == i){
this.Parts[h].normals[k].transform(b.matrix)
}
}
}
}
}
valuecount(){
let count = 0
for (const part of this.Parts) {
count += part.valueCount
}
return count
}
facecount(){
let count = 0
for (const part of this.Parts) {
count += part.facecount
}
return count
}
texcount(){
let count = 0
for (const part of this.Parts) {
count += part.texCount
}
return count
}
}
class DBinfo {
//done
constructor(data) {
let parser = new DOMParser();
let xml = parser.parseFromString(data, "text/xml");
let Version = xml.getElementsByTagName('Bricks')[0].attributes['version'].value
console.log('DB Version: ' + Version);
return ('DB Version: ' + Version);
}
}
class Converter {
constructor(){
this.allMaterials = ''
this.scene = new Scene()
}
LoadDBURL(dbURLlocation){
this.database = new DBURLReader(dbURLlocation)
if(this.database.initok && this.database.fileexist('Materials.xml')){
this.allMaterials = new Materials(this.database.filelist['Materials.xml'].read())
}
}
LoadScene(filename, placement = null){
if(this.database.initok){
this.scene.AddScene(filename, placement)
}
}
Export(filename){
let allMaterials = new Materials(this.database.filelist['Materials.xml'].read())
let invert = new Matrix3D()
//invert.n33 = -1 //uncomment to invert the Z-Axis
let indexOffset = 1
let textOffset = 1
let usedmaterials = []
let geometriecache = {}
let total = this.scene.Bricks.length
let current = 0
for (const cam of this.scene.Scenecamera){
let camm = new THREE.Matrix4();
camm.set(
cam.matrix.n11, cam.matrix.n21, cam.matrix.n31, cam.matrix.n41,
cam.matrix.n12, cam.matrix.n22, cam.matrix.n32, cam.matrix.n42,
cam.matrix.n13, cam.matrix.n23, cam.matrix.n33, cam.matrix.n43,
cam.matrix.n14, cam.matrix.n24+30.0, cam.matrix.n34+24.0, cam.matrix.n44
);
cammatr.getInverse(camm)
}
for (const bri of this.scene.Bricks){
current += 1
for (const pa of bri.Parts){
let geo = 0
if (geometriecache.hasOwnProperty(pa.designID)) {
// console.log(`Re-use brick ${pa.designID}`)
geo = geometriecache[pa.designID]
}
else {
// console.log(`New brick ${pa.designID}`)
geo = new Geometry(pa.designID, this.database)
geometriecache[pa.designID] = geo
}
let ind = 0
let n11 = pa.Bones[ind].matrix.n11
let n12 = pa.Bones[ind].matrix.n12
let n13 = pa.Bones[ind].matrix.n13
let n14 = pa.Bones[ind].matrix.n14
let n21 = pa.Bones[ind].matrix.n21
let n22 = pa.Bones[ind].matrix.n22
let n23 = pa.Bones[ind].matrix.n23
let n24 = pa.Bones[ind].matrix.n24
let n31 = pa.Bones[ind].matrix.n31
let n32 = pa.Bones[ind].matrix.n32
let n33 = pa.Bones[ind].matrix.n33
let n34 = pa.Bones[ind].matrix.n34
let n41 = pa.Bones[ind].matrix.n41
let n42 = pa.Bones[ind].matrix.n42
let n43 = pa.Bones[ind].matrix.n43
let n44 = pa.Bones[ind].matrix.n44
let m = new THREE.Matrix4();
// Only parts with more then 1 bone are flex parts
let flexflag = 1
if (!(pa.Bones.length > flexflag)){
m.set( n11, n21, n31, n41,
n12, n22, n32, n42,
n13, n23, n33, n43,
n14, n24 ,n34, n44);
}
// Move the brick from model space to its place on the property
if (bri.placement){
m.premultiply(bri.placement)
}
let decoCount = 0
for (const [partindex, part] of geo.Parts.entries()){
part.outpositions = Array.from(part.positions);
part.outnormals = Array.from(part.normals);
// translate / rotate only parts with more then 1 bone. This are flex parts.
if (pa.Bones.length > flexflag){
for (const [i, b] of pa.Bones.entries()) {
//positions
for (const [j, p] of part.outpositions.entries()){
if (part.bonemap[j] == i){
p.transform(invert.mul(b.matrix))
}
}
//normals
for (const [k, n] of part.outnormals.entries()){
if (part.bonemap[k] == i){
n.transformW(invert.mul(b.matrix))
}
}
}
}
let parr = []
for (const point of part.outpositions){
parr.push(point.x)
parr.push(point.y)
parr.push(point.z)
}
let threevertices = new Float32Array(parr)
let narr = []
for (const normal of part.outnormals){
narr.push(normal.x)
narr.push(normal.y)
narr.push(normal.z)
}
let threenormals = new Float32Array(narr)
let tarr = []
for (const text of part.textures){
tarr.push(text.x)
// NOTE Three.js maps Textures in from top to bottom so we calculate 1.0 - t so the image will map properly
tarr.push(1 - text.y)
//console.log(text.toString())
}
let threeuvs = new Float32Array(tarr)
let farr =[]
for (const face of part.faces){
farr.push(face.a)
farr.push(face.b)
farr.push(face.c)
//console.log(face.toString())
}
let materialCurrentPart = pa.materials[partindex]
let lddmat = allMaterials.getMaterialbyId(materialCurrentPart)
if (typeof lddmat !== 'undefined'){
//nothing. Everything ok.
}
else {
//lddmat undefined
console.log('partindex: ' + partindex)
console.log(pa.materials)
lddmat = allMaterials.getMaterialbyId(21)
}
let deco = '0'
if (pa.hasOwnProperty('decoration') && geo.Parts[partindex].textures.length > 0 ){
if (decoCount < pa.decoration.length){
deco = pa.decoration[decoCount]
}
decoCount += 1
}
let material = new THREE.MeshPhongMaterial();
material.color.set(lddmat.toString());
material.transparent = true
material.opacity = (lddmat.a / 255)
let material1 = new THREE.MeshPhongMaterial();
let materials = [];
materials.push(material)
let geometry = new THREE.BufferGeometry();
geometry.addGroup( 0, Infinity, 0 );
if (!(deco == '0')){
let DECORATIONPATH = 'Decorations/'
let decofilename = DECORATIONPATH + deco + '.png'
let extfile = this.database.filelist[decofilename].urlHandle
let texture = new THREE.TextureLoader().load( extfile );
material1 = new THREE.MeshPhongMaterial( { map: texture } );
material1.transparent = true
materials.push(material1)
geometry.addGroup( 0, Infinity, 1 );
}
geometry.setAttribute('position', new THREE.BufferAttribute(threevertices, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(threenormals, 3));
geometry.setAttribute('uv', new THREE.BufferAttribute(threeuvs, 2));
geometry.setIndex(farr);
let mesh = new THREE.Mesh(geometry, materials);
mesh.matrixAutoUpdate = false
mesh.matrix = m
{% if not center %}
brick_pos.setFromMatrixPosition( m );
{% endif %}
scene.add(mesh);
// let vnh = new VertexNormalsHelper( mesh, 5 );
// scene.add( vnh );
}
}
}
}
}
class LOCReader {
//done
constructor(data) {
this.offset = 0
this.values = {}
this.data = data
if (this.data[0].charCodeAt() == 50 && this.data[1].charCodeAt() == 0){
this.offset += 2
while (this.offset < this.data.length){
let key = this.NextString().replace('Material', '')
let value = this.NextString()
this.values[key] = value
}
}
}
NextString(){
let out =''
let t = this.data[this.offset].charCodeAt()
this.offset += 1
while (t != 0){
out = out + String.fromCharCode(t)
t = this.data[this.offset].charCodeAt()
this.offset += 1
}
return out;
}
}
class Flex {
//done
constructor(boneId=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0){
this.boneId = boneId
let rotationMatrix = new Matrix3D()
rotationMatrix.rotate(-angle * Math.PI / 180.0, new Point3D(ax, ay, az))
let p = new Point3D(tx, ty, tz)
p.transformW(rotationMatrix)
rotationMatrix.n41 -= p.x
rotationMatrix.n42 -= p.y
rotationMatrix.n43 -= p.z
this.matrix = rotationMatrix
}
}
class Field2D{
//done
constructor(type=0, width=0, height=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0, field2DRawData='none'){
this.type = type
this.field2DRawData = field2DRawData
let rotationMatrix = new Matrix3D()
rotationMatrix.rotate(-angle * Math.PI / 180.0, new Point3D(ax, ay, az))
let p = new Point3D(tx, ty, tz)
p.transformW(rotationMatrix)
rotationMatrix.n41 -= p.x
rotationMatrix.n42 -= p.y
rotationMatrix.n43 -= p.z
this.matrix = rotationMatrix
this.custom2DField = []
let rows_count = height + 1
let cols_count = width + 1
this.custom2DField = new Array(rows_count).fill(new Array(cols_count).fill(0));
let custom2DFieldString = field2DRawData.replace(/[\r\n\x0B\x0C\u0085\u2028\u2029]+/g, '').replace(/\s/g, '')
let custom2DFieldArr = custom2DFieldString.split(',')
let k = 0
for (let i = 0; i < rows_count ;i++) {
for (let j = 0; j < cols_count ;j++){
this.custom2DField[i][j] = custom2DFieldArr[k]
k += 1
}
}
}
}
class Primitive {
//done
constructor(data){
this.Designname = ''
this.Bones = []
this.Fields2D = []
this.PhysicsAttributes = {}
this.Bounding = {}
this.GeometryBounding = {}
let parser = new DOMParser();
let xml = parser.parseFromString(data,"text/xml");
let nodes = xml.firstChild.childNodes;
for (let i = 0; i < nodes.length ;i++) {
let node = nodes[i]
if (node.nodeName == 'Flex'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Bone'){
this.Bones.push(new Flex(parseInt(childnode.getAttribute('boneId')), parseFloat(childnode.getAttribute('angle')), parseFloat(childnode.getAttribute('ax')), parseFloat(childnode.getAttribute('ay')), parseFloat(childnode.getAttribute('az')), parseFloat(childnode.getAttribute('tx')), parseFloat(childnode.getAttribute('ty')), parseFloat(childnode.getAttribute('tz'))))
}
}
}
else if (node.nodeName == 'Annotations'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Annotation' && childnode.hasAttribute('designname')){
this.Designname = childnode.getAttribute('designname')
}
}
}
else if (node.nodeName == 'Connectivity'){
let childnodes = node.childNodes
for (let j = 0; j < childnodes.length ;j++) {
let childnode = childnodes[j]
if (childnode.nodeName == 'Custom2DField'){
this.Fields2D.push(new Field2D(parseInt(childnode.getAttribute('type')), parseInt(childnode.getAttribute('width')), parseInt(childnode.getAttribute('height')), parseFloat(childnode.getAttribute('angle')), parseFloat(childnode.getAttribute('ax')), parseFloat(childnode.getAttribute('ay')), parseFloat(childnode.getAttribute('az')), parseFloat(childnode.getAttribute('tx')), parseFloat(childnode.getAttribute('ty')), parseFloat(childnode.getAttribute('tz')), (childnode.firstChild.data)))
}
}
}
}
}
}
class Materials {
constructor(data) {
this.Materials = {}
let parser = new DOMParser();
let xml = parser.parseFromString(data,"text/xml");
let nodes = xml.firstChild.childNodes;
for (let i = 0; i < nodes.length ;i++) {
let node = nodes[i]
if (node.nodeName == 'Material') {
this.Materials[node.getAttribute('MatID')] = new Material(node.getAttribute('MatID'), parseInt(node.getAttribute('Red')), parseInt(node.getAttribute('Green')), parseInt(node.getAttribute('Blue')), parseInt(node.getAttribute('Alpha')), node.getAttribute('MaterialType'))
}
}
}
getMaterialbyId(mid) {
return this.Materials[mid]
}
}
class Material {
//done
constructor(id, r, g, b, a, mtype) {
this.id = id
this.name = id
this.mattype = mtype
this.r = parseFloat(r)
this.g = parseFloat(g)
this.b = parseFloat(b)
this.a = parseFloat(a)
}
string(){
let out = ('Red: ' + this.r + ' Green: ' + this.g + ' Blue: '+ this.b + ' Aplha: ' + this.a)
return out;
}
toString(){
let out = `rgb(${this.r}, ${this.g}, ${this.b})`
return out;
}
}
function FindDBURL(){
let dburl = '/luclient/ldddb/'
let xhr = new XMLHttpRequest();
xhr.open('GET', dburl, false); // `false` makes the request synchronous
// request state change event
xhr.onreadystatechange = function() {
// request completed?
if (xhr.readyState !== 4) {//return;
dburl = false;
console.log('readyState error in FindDBURL:', xhr.status, xhr.statusText);
}
if (xhr.status === 200) {
// request successful - show response
//console.log(xhr.responseText);
}
else {
// request error
dburl = false;
console.log('HTTP error in FindDBURL:', xhr.status, xhr.statusText);
}
};
// start request
xhr.send();
return dburl
}
class DBURLFile {
constructor(urlHandle, name) {
this.urlHandle = urlHandle
this.name = name
}
read() {
let fileContent
let self = this;
let xhr = new XMLHttpRequest();
xhr.open('GET', self.urlHandle, false);
// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');
// request state change event
xhr.onreadystatechange = function() {
// request completed?
if (xhr.readyState !== 4) {//return;
console.log('readyState error in DBURLFile:', xhr.status, xhr.statusText);
}
if (xhr.status === 200) {
// request successful - show response
fileContent = xhr.responseText;
}
else {
// request error
console.log('HTTP error in DBURLFile:', xhr.status, xhr.statusText);
}
};
// start request
xhr.send();
return fileContent
}
}
class DBURLReader {
constructor(dburl) {
this.filelist = {};
this.initok = false;
this.location = dburl;
this.dbinfo = '';
this.parse(this.location);
// console.log(JSON.stringify(this.filelist))
if(this.fileexist('Materials.xml') && this.fileexist('info.xml')){
this.dbinfo = new DBinfo(this.filelist['info.xml'].read());
this.initok = true
}
else{
alert("db url ERROR")
}
}
fileexist(filename) {
let self = this;
return self.filelist[filename];
}
parse(dburl, folder="") {
let self = this;
let xhr = new XMLHttpRequest();
xhr.open('GET', dburl, false);
// request state change event
xhr.onreadystatechange = function() {
// request completed?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful - show response
let data = JSON.parse(xhr.responseText)
//console.log(JSON.stringify(data, null, "\t"));
for(let i = 0; i < data.length; i++) {
let obj = data[i];
if (obj.type == 'directory'){
// parse subdirs
self.parse(dburl + obj.name + '/', obj.name)
}
else if (obj.type == 'file'){
self.filelist[obj.name] = new DBURLFile(dburl + obj.name, obj.name)
}
else {
console.log('Strange object parsed: ' + obj.type)
}
}
}
else {
// request error
console.log('HTTP error', xhr.status, xhr.statusText);
}
};
// start request
xhr.send();
}
}
//Start
// Player-built models with the position and rotation stored for them in properties_contents
let lxfml_file_list = [
{% for model in content %}
{% if model.lot == 14 %}
{
"url": "{{url_for('properties.get_model', id=model.id, file_format='lxfml', lod=lod)}}",
"pos": {{ model.pos[0]|tojson }}
}{{ ", " if not loop.last else "" }}
{% endif %}
{% endfor %}
]
if (lxfml_file_list.length > 0) {
let ldddburl = FindDBURL()
if (ldddburl) {
let converter = new Converter()
converter.LoadDBURL(ldddburl)
for (let i = 0; i < lxfml_file_list.length; i++) {
let pos = lxfml_file_list[i].pos
let placement = new THREE.Matrix4().compose(
new THREE.Vector3(pos.x, pos.y, pos.z),
new THREE.Quaternion(pos.rx, pos.ry, pos.rz, pos.rw),
new THREE.Vector3(1, 1, 1)
)
converter.LoadScene(lxfml_file_list[i].url, placement)
}
converter.Export('test.webgl')
}
else {
alert("LDD database not available. Please look for LEGO-Digital-Designer database.")
}
}
const onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
const percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round( percentComplete, 2 ) + '% downloaded' );
}
};
const onError = function (error) {
console.log(error)
};
// Load in pre-built models
let obj_file_list = [
{% for model in content %}
{% if model.lot != 14 %}
{{ model }} {{ ", " if not loop.last else "" }}
{% endif %}
{% endfor %}
]
for (let i = 0; i < obj_file_list.length; i++) {
let mtlLoader = new MTLLoader();
mtlLoader.load( obj_file_list[i].mtl, function( materials ) {
materials.preload();
let objLoader = new OBJLoader();
objLoader.setMaterials( materials );
objLoader.load( obj_file_list[i].obj, function ( object ) {
// console.log(obj_file_list[i].pos)
for (let j = 0; j < obj_file_list[i].pos.length; j++) {
// console.log(obj_file_list[i].pos[j])
var newModel = object.clone();
newModel.position.x = obj_file_list[i].pos[j].x;
newModel.position.y = obj_file_list[i].pos[j].y;
newModel.position.z = obj_file_list[i].pos[j].z;
let quaternion = new THREE.Quaternion(
obj_file_list[i].pos[j].rx,
obj_file_list[i].pos[j].ry,
obj_file_list[i].pos[j].rz,
obj_file_list[i].pos[j].rw
);
newModel.rotation.setFromQuaternion(quaternion)
scene.add( newModel );
};
}, onProgress, onError );
});
}
// Three.JS stuff
let container = document.createElement( 'div' );
document.body.appendChild( container );
let camera = new THREE.PerspectiveCamera( 2.5, window.innerWidth / window.innerHeight, 1, 10000 );
{% if center %}
camera.position.set( brick_pos.x + 800, brick_pos.y + 800, brick_pos.z + 800 );
{% elif content|length > 1 %}
camera.position.set( {{ content[0].x }}+ 200, {{ content[0].y }}+ 300, {{ content[0].z }}+ 200 );
{% else %}
camera.position.set( brick_pos.x + 200, brick_pos.y + 300, brick_pos.z + 200 );
{% endif %}
{% if center %}
let center = new THREE.Vector3{{ center }};
let groundTexture = new THREE.TextureLoader().load( "{{url_for('luclient.get_dds_as_png', filename='env_nim_ag_grass.dds')}}");
groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set( 10, 10 );
groundTexture.anisotropy = 16;
groundTexture.encoding = THREE.sRGBEncoding;
let groundMaterial = new THREE.MeshStandardMaterial( { map: groundTexture } );
let mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 400, 400 ), groundMaterial );
mesh.position.y = center.y;
mesh.position.x = center.x;
mesh.position.z = center.z
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
scene.add( mesh );
{% endif %}
// scene
scene.background = new THREE.Color( 0xdeebed );
let ambientLight = new THREE.AmbientLight( 0xdeebed, 0.4 );
scene.add( ambientLight );
let directionalLight = new THREE.DirectionalLight( 0xffffff, 0.75 );
directionalLight.position.set( - 10000, 12000, 15000 );
scene.add( directionalLight );
let directionalLight2 = new THREE.DirectionalLight( 0xffffff, 0.75 );
directionalLight2.position.set( 10000, 12000, -15000 );
scene.add( directionalLight2 );
let renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
let controls = new THREE.OrbitControls (camera, renderer.domElement);
{% if content|length == 1 and content[0].lot != 14 %}
controls.target = new THREE.Vector3({{ content[0].pos[0].x }}, {{ content[0].pos[0].y }}, {{ content[0].pos[0].z }})
{% else %}
controls.target = brick_pos
{% endif %}
controls.update()
// if ( vnh ) vnh.update();
let render = function () {
requestAnimationFrame(render);
// camera.rotation.z += 0.01;
renderer.render(scene, camera);
};
render();
</script>
</body>
</html>